[client/idd] network: add recovery, cursor, and control

This commit is contained in:
Geoffrey McRae
2026-09-30 20:55:56 +10:00
parent 6dfba7519c
commit a67034b14f
15 changed files with 3241 additions and 101 deletions

View File

@@ -7,9 +7,12 @@ endif()
set(TRANSPORT_NETWORK_SOURCES set(TRANSPORT_NETWORK_SOURCES
connection.c connection.c
control.c
cursor.c
input.c input.c
known_hosts.c known_hosts.c
network.c network.c
recovery.c
) )
if(WIN32) if(WIN32)

View File

@@ -30,6 +30,7 @@
#include <LGProtocol/NetworkPacket.h> #include <LGProtocol/NetworkPacket.h>
#include <LGProtocol/NetworkRoute.h> #include <LGProtocol/NetworkRoute.h>
#include <LGProtocol/NetworkSequence.h> #include <LGProtocol/NetworkSequence.h>
#include <LGProtocol/NetworkServices.h>
#include <LGProtocol/NetworkStream.h> #include <LGProtocol/NetworkStream.h>
#include <msquic.h> #include <msquic.h>
@@ -49,7 +50,8 @@
#define NETWORK_KEEPALIVE_INTERVAL_MS 1000U #define NETWORK_KEEPALIVE_INTERVAL_MS 1000U
#define NETWORK_INITIAL_RTT_MS 1U #define NETWORK_INITIAL_RTT_MS 1U
#define NETWORK_MAX_PEER_STREAMS 16U #define NETWORK_MAX_PEER_STREAMS 16U
#define NETWORK_MAX_CONTROL_PAYLOAD (1024U * 1024U) #define NETWORK_MAX_CONTROL_PAYLOAD \
(LG_NET_CURSOR_SHAPE_HEADER_WIRE_SIZE + LG_NET_CURSOR_MAX_SHAPE_LENGTH)
#define NETWORK_MAX_CONTROL_BUFFER \ #define NETWORK_MAX_CONTROL_BUFFER \
(2U * (LG_NET_ENVELOPE_WIRE_SIZE + NETWORK_MAX_CONTROL_PAYLOAD)) (2U * (LG_NET_ENVELOPE_WIRE_SIZE + NETWORK_MAX_CONTROL_PAYLOAD))
#define NETWORK_MAX_DATAGRAM_PAYLOAD 1136U #define NETWORK_MAX_DATAGRAM_PAYLOAD 1136U
@@ -98,7 +100,7 @@ struct NetworkConnection
NetworkConnectionStateFn state; NetworkConnectionStateFn state;
void * opaque; void * opaque;
LGNetCapability offeredCapabilities[LG_NET_SERVICE_USB + 1U]; LGNetCapability offeredCapabilities[LG_NET_SERVICE_CONTROL + 1U];
uint32_t offeredCapabilityCount; uint32_t offeredCapabilityCount;
uint16_t offeredServices; uint16_t offeredServices;
@@ -128,13 +130,13 @@ struct NetworkConnection
uint16_t receivedServices; uint16_t receivedServices;
uint16_t reliableServices; uint16_t reliableServices;
uint16_t datagramServices; uint16_t datagramServices;
uint16_t serviceVersions[LG_NET_SERVICE_USB + 1U]; uint16_t serviceVersions[LG_NET_SERVICE_CONTROL + 1U];
uint32_t maxPayloadLength; uint32_t maxPayloadLength;
uint32_t maxDatagramPayload; uint32_t maxDatagramPayload;
uint16_t maxDatagramSendLength; uint16_t maxDatagramSendLength;
bool datagramSendEnabled; bool datagramSendEnabled;
uint32_t serviceMaxPayload[LG_NET_SERVICE_USB + 1U]; uint32_t serviceMaxPayload[LG_NET_SERVICE_CONTROL + 1U];
uint32_t serviceMaxDatagram[LG_NET_SERVICE_USB + 1U]; uint32_t serviceMaxDatagram[LG_NET_SERVICE_CONTROL + 1U];
uint8_t clientHelloNonce[LG_NET_HELLO_NONCE_LENGTH]; uint8_t clientHelloNonce[LG_NET_HELLO_NONCE_LENGTH];
uint8_t serverHelloNonce[LG_NET_HELLO_NONCE_LENGTH]; uint8_t serverHelloNonce[LG_NET_HELLO_NONCE_LENGTH];
@@ -185,7 +187,8 @@ static bool networkCapabilityOfferValid(
if (!lgNetCapabilityValid(capability) || if (!lgNetCapabilityValid(capability) ||
!version || !version ||
capability->service <= LG_NET_SERVICE_CORE || capability->service <= LG_NET_SERVICE_CORE ||
capability->service >= LG_NET_SERVICE_USB || capability->service > LG_NET_SERVICE_CONTROL ||
capability->service == LG_NET_SERVICE_USB ||
capability->flags & LG_NET_CAPABILITY_SELECTED || capability->flags & LG_NET_CAPABILITY_SELECTED ||
capability->versionMin < version->minimum || capability->versionMin < version->minimum ||
capability->versionMax > version->maximum || capability->versionMax > version->maximum ||
@@ -196,6 +199,12 @@ static bool networkCapabilityOfferValid(
return true; return true;
} }
static bool networkConnectionServiceValid(LGNetService service)
{
return service > LG_NET_SERVICE_CORE &&
service <= LG_NET_SERVICE_CONTROL && service != LG_NET_SERVICE_USB;
}
static bool networkBytesNonzero(const uint8_t * data, size_t size) static bool networkBytesNonzero(const uint8_t * data, size_t size)
{ {
if (!data) if (!data)
@@ -265,6 +274,8 @@ static bool networkConnectionSendPacket(NetworkConnection * this,
LG_NET_MESSAGE_VERSION_CURRENT); LG_NET_MESSAGE_VERSION_CURRENT);
if (payloadLength > NETWORK_MAX_CONTROL_PAYLOAD || if (payloadLength > NETWORK_MAX_CONTROL_PAYLOAD ||
(payloadLength && !payload) || (payloadLength && !payload) ||
(service != LG_NET_SERVICE_CORE &&
!networkConnectionServiceValid(service)) ||
(flags & (flags &
(LG_NET_ENVELOPE_DATAGRAM | LG_NET_ENVELOPE_EARLY_DATA)) || (LG_NET_ENVELOPE_DATAGRAM | LG_NET_ENVELOPE_EARLY_DATA)) ||
!lgNetEnvelopeRouteValid(&routeEnvelope, LG_NET_ROLE_CLIENT, !lgNetEnvelopeRouteValid(&routeEnvelope, LG_NET_ROLE_CLIENT,
@@ -364,8 +375,10 @@ static bool networkConnectionSendHello(NetworkConnection * this)
goto cleanup; goto cleanup;
for (LGNetService service = LG_NET_SERVICE_RECOVERY; for (LGNetService service = LG_NET_SERVICE_RECOVERY;
service < LG_NET_SERVICE_USB; ++service) service <= LG_NET_SERVICE_CONTROL; ++service)
{ {
if (service == LG_NET_SERVICE_USB)
continue;
if (!(this->offeredServices & (UINT16_C(1) << service))) if (!(this->offeredServices & (UINT16_C(1) << service)))
continue; continue;
@@ -426,8 +439,7 @@ static bool networkConnectionHandleCapability(NetworkConnection * this,
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK) packet->payload, packet->payloadLength) != LG_NET_PARSE_OK)
return false; return false;
if (capability.service <= LG_NET_SERVICE_CORE || if (!networkConnectionServiceValid(capability.service))
capability.service >= LG_NET_SERVICE_USB)
return false; return false;
LG_LOCK(this->lock); LG_LOCK(this->lock);
@@ -486,8 +498,10 @@ static bool networkConnectionRequiredCapabilitiesSelected(
const NetworkConnection * this) const NetworkConnection * this)
{ {
for (LGNetService service = LG_NET_SERVICE_RECOVERY; for (LGNetService service = LG_NET_SERVICE_RECOVERY;
service < LG_NET_SERVICE_USB; ++service) service <= LG_NET_SERVICE_CONTROL; ++service)
{ {
if (service == LG_NET_SERVICE_USB)
continue;
const uint16_t serviceMask = UINT16_C(1) << service; const uint16_t serviceMask = UINT16_C(1) << service;
if ((this->offeredServices & serviceMask) && if ((this->offeredServices & serviceMask) &&
(this->offeredCapabilities[service].flags & (this->offeredCapabilities[service].flags &
@@ -518,6 +532,11 @@ static void networkConnectionSetSessionFeatures(NetworkConnection * this)
this->session.features |= LG_TRANSPORT_FEATURE_CLIPBOARD; this->session.features |= LG_TRANSPORT_FEATURE_CLIPBOARD;
if (services & (UINT16_C(1) << LG_NET_SERVICE_AUDIO)) if (services & (UINT16_C(1) << LG_NET_SERVICE_AUDIO))
this->session.features |= LG_TRANSPORT_FEATURE_AUDIO; this->session.features |= LG_TRANSPORT_FEATURE_AUDIO;
if (services & (UINT16_C(1) << LG_NET_SERVICE_CONTROL))
this->session.features |=
LG_TRANSPORT_FEATURE_SET_CURSOR_POS |
LG_TRANSPORT_FEATURE_WINDOW_SIZE |
LG_TRANSPORT_FEATURE_FRAME_SCHEDULE;
} }
static bool networkConnectionHandleAuthChallenge(NetworkConnection * this, static bool networkConnectionHandleAuthChallenge(NetworkConnection * this,
@@ -899,7 +918,8 @@ static bool networkConnectionHandlePacket(NetworkConnection * this,
if (packet->envelope.service == LG_NET_SERVICE_CORE) if (packet->envelope.service == LG_NET_SERVICE_CORE)
return networkConnectionHandleCore(this, packet); return networkConnectionHandleCore(this, packet);
if (!atomic_load_explicit(&this->sessionValid, memory_order_acquire)) if (!networkConnectionServiceValid(packet->envelope.service) ||
!atomic_load_explicit(&this->sessionValid, memory_order_acquire))
return false; return false;
LG_LOCK(this->lock); LG_LOCK(this->lock);
@@ -930,12 +950,16 @@ static bool networkConnectionValidateControl(NetworkConnection * this,
maxPayloadLength = this->maxPayloadLength; maxPayloadLength = this->maxPayloadLength;
LG_UNLOCK(this->lock); LG_UNLOCK(this->lock);
const bool intermediate =
(packet->envelope.service == LG_NET_SERVICE_RECOVERY &&
packet->envelope.messageType == LG_NET_RECOVERY_MESSAGE_STATUS) ||
(packet->envelope.service == LG_NET_SERVICE_CONTROL &&
packet->envelope.messageType == LG_NET_CONTROL_MESSAGE_STATUS);
return packet->envelope.headerSize == LG_NET_ENVELOPE_WIRE_SIZE && return packet->envelope.headerSize == LG_NET_ENVELOPE_WIRE_SIZE &&
packet->payloadLength <= maxPayloadLength && packet->payloadLength <= maxPayloadLength &&
packet->envelope.messageVersion == LG_NET_MESSAGE_VERSION_CURRENT && packet->envelope.messageVersion == LG_NET_MESSAGE_VERSION_CURRENT &&
(packet->envelope.flags & (packet->envelope.flags & LG_NET_ENVELOPE_RELIABLE) &&
(LG_NET_ENVELOPE_RELIABLE | LG_NET_ENVELOPE_FINAL)) == (intermediate || (packet->envelope.flags & LG_NET_ENVELOPE_FINAL)) &&
(LG_NET_ENVELOPE_RELIABLE | LG_NET_ENVELOPE_FINAL) &&
!(packet->envelope.flags & !(packet->envelope.flags &
(LG_NET_ENVELOPE_DATAGRAM | LG_NET_ENVELOPE_EARLY_DATA)) && (LG_NET_ENVELOPE_DATAGRAM | LG_NET_ENVELOPE_EARLY_DATA)) &&
lgNetEnvelopeRouteValid(&packet->envelope, LG_NET_ROLE_SERVER, lgNetEnvelopeRouteValid(&packet->envelope, LG_NET_ROLE_SERVER,
@@ -956,6 +980,8 @@ static bool networkConnectionDecodeDatagram(NetworkConnection * this,
if (!atomic_load_explicit( if (!atomic_load_explicit(
&this->sessionValid, memory_order_acquire) || &this->sessionValid, memory_order_acquire) ||
(packet->envelope.service != LG_NET_SERVICE_CORE &&
!networkConnectionServiceValid(packet->envelope.service)) ||
packet->envelope.headerSize != LG_NET_ENVELOPE_WIRE_SIZE || packet->envelope.headerSize != LG_NET_ENVELOPE_WIRE_SIZE ||
!packet->envelope.sequence || !packet->envelope.sequence ||
packet->envelope.messageVersion != LG_NET_MESSAGE_VERSION_CURRENT || packet->envelope.messageVersion != LG_NET_MESSAGE_VERSION_CURRENT ||
@@ -1645,7 +1671,7 @@ bool networkConnectionServiceAvailable(NetworkConnection * this,
LGNetService service, LGNetCapabilityFlags required) LGNetService service, LGNetCapabilityFlags required)
{ {
if (!networkConnectionValid(this) || if (!networkConnectionValid(this) ||
service <= LG_NET_SERVICE_CORE || service > LG_NET_SERVICE_USB || !networkConnectionServiceValid(service) ||
required & ~(LG_NET_CAPABILITY_RELIABLE | required & ~(LG_NET_CAPABILITY_RELIABLE |
LG_NET_CAPABILITY_DATAGRAM)) LG_NET_CAPABILITY_DATAGRAM))
return false; return false;
@@ -1666,7 +1692,7 @@ bool networkConnectionServiceVersion(NetworkConnection * this,
LGNetService service, uint16_t * version) LGNetService service, uint16_t * version)
{ {
if (!version || !networkConnectionValid(this) || if (!version || !networkConnectionValid(this) ||
service <= LG_NET_SERVICE_CORE || service >= LG_NET_SERVICE_USB) !networkConnectionServiceValid(service))
return false; return false;
LG_LOCK(this->lock); LG_LOCK(this->lock);
@@ -1703,7 +1729,8 @@ LG_TransportStatus networkConnectionSendDatagram(NetworkConnection * this,
LGNetEnvelope routeEnvelope; LGNetEnvelope routeEnvelope;
lgNetEnvelopeInit(&routeEnvelope, service, messageType, serviceVersion, lgNetEnvelopeInit(&routeEnvelope, service, messageType, serviceVersion,
LG_NET_MESSAGE_VERSION_CURRENT); LG_NET_MESSAGE_VERSION_CURRENT);
if ((payloadLength && !payload) || service == LG_NET_SERVICE_CORE || if ((payloadLength && !payload) ||
!networkConnectionServiceValid(service) ||
flags & (LG_NET_ENVELOPE_RELIABLE | LG_NET_ENVELOPE_DATAGRAM | flags & (LG_NET_ENVELOPE_RELIABLE | LG_NET_ENVELOPE_DATAGRAM |
LG_NET_ENVELOPE_EARLY_DATA) || LG_NET_ENVELOPE_EARLY_DATA) ||
!lgNetEnvelopeRouteValid(&routeEnvelope, LG_NET_ROLE_CLIENT, !lgNetEnvelopeRouteValid(&routeEnvelope, LG_NET_ROLE_CLIENT,

View File

@@ -0,0 +1,369 @@
/**
* 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 "control.h"
#include "common/locking.h"
#include <LGProtocol/NetworkServices.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#define NETWORK_CONTROL_STATUS_CAPACITY 64U
struct NetworkControlEntry
{
uint64_t id;
LGNetControlStatusCode status;
bool used;
};
struct NetworkControl
{
NetworkConnection * connection;
LG_Lock lock;
bool connected;
bool available;
uint16_t serviceVersion;
uint64_t nextID;
unsigned nextEntry;
struct NetworkControlEntry entries[NETWORK_CONTROL_STATUS_CAPACITY];
};
static uint64_t networkControlNextID(uint64_t id)
{
++id;
return id ? id : 1U;
}
static bool networkControlTerminal(LGNetControlStatusCode status)
{
return status != LG_NET_CONTROL_STATUS_PENDING;
}
static struct NetworkControlEntry * networkControlFindNL(
NetworkControl * control, uint64_t id)
{
for (size_t index = 0; index < NETWORK_CONTROL_STATUS_CAPACITY; ++index)
if (control->entries[index].used && control->entries[index].id == id)
return &control->entries[index];
return NULL;
}
static struct NetworkControlEntry * networkControlReserveNL(
NetworkControl * control, uint64_t id)
{
for (size_t offset = 0;
offset < NETWORK_CONTROL_STATUS_CAPACITY; ++offset)
{
const unsigned index =
(control->nextEntry + offset) % NETWORK_CONTROL_STATUS_CAPACITY;
struct NetworkControlEntry * entry = &control->entries[index];
if (entry->used && !networkControlTerminal(entry->status))
continue;
*entry = (struct NetworkControlEntry)
{
.id = id,
.status = LG_NET_CONTROL_STATUS_PENDING,
.used = true,
};
control->nextEntry =
(index + 1U) % NETWORK_CONTROL_STATUS_CAPACITY;
return entry;
}
return NULL;
}
bool networkControlCreate(NetworkControl ** result,
NetworkConnection * connection)
{
if (!result || !connection)
return false;
NetworkControl * control = calloc(1, sizeof(*control));
if (!control)
return false;
LG_LOCK_INIT(control->lock);
control->connection = connection;
*result = control;
return true;
}
void networkControlDestroy(NetworkControl ** result)
{
if (!result || !*result)
return;
NetworkControl * control = *result;
LG_LOCK_FREE(control->lock);
free(control);
*result = NULL;
}
void networkControlConnectionState(NetworkControl * control, bool connected)
{
uint16_t version = 0;
const bool available = connected &&
networkConnectionServiceAvailable(control->connection,
LG_NET_SERVICE_CONTROL, LG_NET_CAPABILITY_RELIABLE) &&
networkConnectionServiceVersion(control->connection,
LG_NET_SERVICE_CONTROL, &version);
LG_LOCK(control->lock);
control->connected = connected;
control->available = available;
control->serviceVersion = available ? version : 0;
control->nextEntry = 0;
memset(control->entries, 0, sizeof(control->entries));
LG_UNLOCK(control->lock);
}
bool networkControlPacket(NetworkControl * control,
const LGNetPacketView * packet, bool datagram)
{
if (!control || !packet || datagram ||
packet->envelope.service != LG_NET_SERVICE_CONTROL ||
packet->envelope.messageType != LG_NET_CONTROL_MESSAGE_STATUS ||
packet->envelope.componentEpoch != 0 ||
!packet->envelope.requestID)
return false;
LGNetControlStatus status;
if (lgNetControlStatusDecode(&status,
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
status.controlID != packet->envelope.requestID)
return false;
const bool terminal =
status.status != LG_NET_CONTROL_STATUS_PENDING;
const LGNetEnvelopeFlags expected =
LG_NET_ENVELOPE_RESPONSE |
LG_NET_ENVELOPE_RELIABLE |
(terminal ? LG_NET_ENVELOPE_FINAL : 0);
if (packet->envelope.flags != expected)
return false;
LG_LOCK(control->lock);
struct NetworkControlEntry * entry =
networkControlFindNL(control, status.controlID);
const bool valid =
control->connected && control->available && entry;
if (valid)
entry->status = status.status;
LG_UNLOCK(control->lock);
return valid;
}
static bool networkControlFrameFlags(
LG_TransportFrameScheduleFlags source,
LGNetControlFrameScheduleFlags * destination)
{
const LG_TransportFrameScheduleFlags known =
LG_TRANSPORT_FRAME_SCHEDULE_ACTIVE |
LG_TRANSPORT_FRAME_SCHEDULE_RELEASE |
LG_TRANSPORT_FRAME_SCHEDULE_RESET |
LG_TRANSPORT_FRAME_SCHEDULE_IMMEDIATE;
if (!destination || !source || (source & ~known))
return false;
*destination = 0;
if (source & LG_TRANSPORT_FRAME_SCHEDULE_ACTIVE)
*destination |= LG_NET_CONTROL_FRAME_SCHEDULE_ACTIVE;
if (source & LG_TRANSPORT_FRAME_SCHEDULE_RELEASE)
*destination |= LG_NET_CONTROL_FRAME_SCHEDULE_RELEASE;
if (source & LG_TRANSPORT_FRAME_SCHEDULE_RESET)
*destination |= LG_NET_CONTROL_FRAME_SCHEDULE_RESET;
if (source & LG_TRANSPORT_FRAME_SCHEDULE_IMMEDIATE)
*destination |= LG_NET_CONTROL_FRAME_SCHEDULE_IMMEDIATE;
return true;
}
LG_TransportStatus networkControlSend(NetworkControl * control,
const LG_TransportControl * request, LG_TransportControlToken * token)
{
if (!control || !request || !token)
return LG_TRANSPORT_ERROR;
uint64_t id;
uint16_t version;
LG_LOCK(control->lock);
if (!control->connected)
{
LG_UNLOCK(control->lock);
return LG_TRANSPORT_DISCONNECTED;
}
if (!control->available)
{
LG_UNLOCK(control->lock);
return LG_TRANSPORT_UNAVAILABLE;
}
id = networkControlNextID(control->nextID);
if (!networkControlReserveNL(control, id))
{
LG_UNLOCK(control->lock);
return LG_TRANSPORT_UNAVAILABLE;
}
control->nextID = id;
version = control->serviceVersion;
LG_UNLOCK(control->lock);
uint8_t payload[LG_NET_CONTROL_FRAME_SCHEDULE_WIRE_SIZE];
uint32_t payloadLength;
uint16_t messageType;
bool encoded;
switch (request->type)
{
case LG_TRANSPORT_CONTROL_SET_CURSOR_POS:
{
const LGNetControlCursorPosition position =
{
.controlID = id,
.x = request->cursorPos.x,
.y = request->cursorPos.y,
};
messageType = LG_NET_CONTROL_MESSAGE_CURSOR_POSITION;
payloadLength = LG_NET_CONTROL_CURSOR_POSITION_WIRE_SIZE;
encoded = lgNetControlCursorPositionEncode(
payload, payloadLength, &position);
break;
}
case LG_TRANSPORT_CONTROL_WINDOW_SIZE:
{
const LGNetControlDisplaySize display =
{
.controlID = id,
.width = request->windowSize.width,
.height = request->windowSize.height,
};
messageType = LG_NET_CONTROL_MESSAGE_DISPLAY_SIZE;
payloadLength = LG_NET_CONTROL_DISPLAY_SIZE_WIRE_SIZE;
encoded = lgNetControlDisplaySizeEncode(
payload, payloadLength, &display);
break;
}
case LG_TRANSPORT_CONTROL_FRAME_SCHEDULE:
{
LGNetControlFrameScheduleFlags flags;
if (!networkControlFrameFlags(
request->frameSchedule.flags, &flags))
encoded = false;
else
{
const LGNetControlFrameSchedule schedule =
{
.controlID = id,
.generation = request->frameSchedule.generation,
.flags = flags,
.periodNs = request->frameSchedule.period,
.targetSlackNs = request->frameSchedule.targetSlack,
.phaseErrorNs = request->frameSchedule.phaseError,
.feedbackFrameSerial =
request->frameSchedule.feedbackFrameSerial,
.feedbackScheduleEpoch =
request->frameSchedule.feedbackScheduleEpoch,
.feedbackDeadlineSerial =
request->frameSchedule.feedbackDeadlineSerial,
.leaseMs = request->frameSchedule.lease,
};
encoded = lgNetControlFrameScheduleEncode(
payload, sizeof(payload), &schedule);
}
messageType = LG_NET_CONTROL_MESSAGE_FRAME_SCHEDULE;
payloadLength = LG_NET_CONTROL_FRAME_SCHEDULE_WIRE_SIZE;
break;
}
default:
encoded = false;
messageType = 0;
payloadLength = 0;
break;
}
LG_TransportStatus status = LG_TRANSPORT_ERROR;
if (encoded)
status = networkConnectionSend(control->connection,
LG_NET_SERVICE_CONTROL, messageType, version,
LG_NET_ENVELOPE_ACK_REQUIRED | LG_NET_ENVELOPE_FINAL,
0, id, payload, payloadLength);
if (status != LG_TRANSPORT_OK)
{
LG_LOCK(control->lock);
struct NetworkControlEntry * entry = networkControlFindNL(control, id);
if (entry)
*entry = (struct NetworkControlEntry) { 0 };
LG_UNLOCK(control->lock);
return status;
}
*token = id;
return LG_TRANSPORT_OK;
}
LG_TransportStatus networkControlStatus(NetworkControl * control,
LG_TransportControlToken token)
{
if (!control || !token)
return LG_TRANSPORT_ERROR;
LG_LOCK(control->lock);
if (!control->connected)
{
LG_UNLOCK(control->lock);
return LG_TRANSPORT_DISCONNECTED;
}
if (!control->available)
{
LG_UNLOCK(control->lock);
return LG_TRANSPORT_UNAVAILABLE;
}
const struct NetworkControlEntry * entry =
networkControlFindNL(control, token);
const LGNetControlStatusCode status = entry ? entry->status : 0;
LG_UNLOCK(control->lock);
switch (status)
{
case LG_NET_CONTROL_STATUS_APPLIED:
return LG_TRANSPORT_OK;
case LG_NET_CONTROL_STATUS_PENDING:
return LG_TRANSPORT_UNAVAILABLE;
case LG_NET_CONTROL_STATUS_BUSY:
case LG_NET_CONTROL_STATUS_UNSUPPORTED:
case LG_NET_CONTROL_STATUS_STALE:
case LG_NET_CONTROL_STATUS_INVALID:
case LG_NET_CONTROL_STATUS_ERROR:
default:
return LG_TRANSPORT_ERROR;
}
}

View File

@@ -0,0 +1,44 @@
/**
* 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
*/
#pragma once
#include "connection.h"
#include "interface/transport.h"
#include <LGProtocol/NetworkProtocol.h>
#include <stdbool.h>
typedef struct NetworkControl NetworkControl;
bool networkControlCreate(NetworkControl ** control,
NetworkConnection * connection);
void networkControlDestroy(NetworkControl ** control);
void networkControlConnectionState(NetworkControl * control, bool connected);
bool networkControlPacket(NetworkControl * control,
const LGNetPacketView * packet, bool datagram);
LG_TransportStatus networkControlSend(NetworkControl * control,
const LG_TransportControl * request, LG_TransportControlToken * token);
LG_TransportStatus networkControlStatus(NetworkControl * control,
LG_TransportControlToken token);

View File

@@ -0,0 +1,813 @@
/**
* 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 "cursor.h"
#include "common/event.h"
#include "common/locking.h"
#include <LGProtocol/NetworkServices.h>
#include <limits.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#define NETWORK_CURSOR_QUEUE_CAPACITY 64U
struct NetworkCursorItem
{
struct NetworkCursorItem * next;
LG_TransportPointer pointer;
uint8_t * shape;
KVMFRColorTransform * colorTransform;
};
struct NetworkCursorStatusReply
{
bool send;
uint64_t stateSequence;
uint64_t appliedShapeID;
uint64_t appliedPositionID;
uint64_t appliedTransformID;
LGNetCursorStatusCode status;
uint32_t detail;
};
struct NetworkCursor
{
NetworkConnection * connection;
LGEvent * event;
LG_Lock lock;
bool connected;
bool available;
bool cancelled;
uint16_t serviceVersion;
struct NetworkCursorItem * head;
struct NetworkCursorItem * tail;
struct NetworkCursorItem * outstanding;
unsigned count;
LGNetCursorShape shape;
uint8_t * shapeData;
bool shapeValid;
bool shapeSupported;
LGNetCursorPosition position;
bool positionValid;
bool positionSupported;
LGNetCursorTransform transform;
bool transformValid;
bool transformSupported;
LGNetCursorState pendingState;
bool pendingStateValid;
bool stateApplied;
uint64_t appliedStateSequence;
uint64_t appliedShapeID;
uint64_t appliedPositionID;
uint64_t appliedTransformID;
bool visible;
uint64_t colorUpdateID;
uint32_t sdrWhiteLevel;
};
static void networkCursorItemFree(struct NetworkCursorItem * item)
{
if (!item)
return;
free(item->colorTransform);
free(item->shape);
free(item);
}
static void networkCursorQueueClearNL(NetworkCursor * cursor)
{
while (cursor->head)
{
struct NetworkCursorItem * item = cursor->head;
cursor->head = item->next;
networkCursorItemFree(item);
}
cursor->tail = NULL;
cursor->count = 0;
}
static void networkCursorCacheClearNL(NetworkCursor * cursor)
{
free(cursor->shapeData);
cursor->shapeData = NULL;
cursor->shape = (LGNetCursorShape) { 0 };
cursor->shapeValid = false;
cursor->shapeSupported = false;
cursor->position = (LGNetCursorPosition) { 0 };
cursor->positionValid = false;
cursor->positionSupported = false;
cursor->transform = (LGNetCursorTransform) { 0 };
cursor->transformValid = false;
cursor->transformSupported = false;
cursor->pendingState = (LGNetCursorState) { 0 };
cursor->pendingStateValid = false;
cursor->stateApplied = false;
cursor->appliedStateSequence = 0;
cursor->appliedShapeID = 0;
cursor->appliedPositionID = 0;
cursor->appliedTransformID = 0;
cursor->visible = false;
cursor->colorUpdateID = 0;
cursor->sdrWhiteLevel = LG_SDR_WHITE_LEVEL_DEFAULT;
}
static bool networkCursorItemCoalescible(
const struct NetworkCursorItem * item)
{
return item && !(item->pointer.flags &
(LG_TRANSPORT_POINTER_SHAPE |
LG_TRANSPORT_POINTER_COLOR_TRANSFORM));
}
static bool networkCursorQueueNL(NetworkCursor * cursor,
struct NetworkCursorItem * item)
{
if (networkCursorItemCoalescible(item) &&
networkCursorItemCoalescible(cursor->tail))
{
cursor->tail->pointer = item->pointer;
networkCursorItemFree(item);
lgSignalEvent(cursor->event);
return true;
}
if (cursor->count == NETWORK_CURSOR_QUEUE_CAPACITY)
{
struct NetworkCursorItem * previous = NULL;
struct NetworkCursorItem * current = cursor->head;
while (current && !networkCursorItemCoalescible(current))
{
previous = current;
current = current->next;
}
if (!current)
return false;
if (previous)
previous->next = current->next;
else
cursor->head = current->next;
if (cursor->tail == current)
cursor->tail = previous;
--cursor->count;
networkCursorItemFree(current);
}
item->next = NULL;
if (cursor->tail)
cursor->tail->next = item;
else
cursor->head = item;
cursor->tail = item;
++cursor->count;
lgSignalEvent(cursor->event);
return true;
}
static bool networkCursorShapeType(LGNetCursorFormat format,
KVMFRCursorType * type)
{
switch (format)
{
case LG_NET_CURSOR_FORMAT_BGRA8_PREMULTIPLIED:
case LG_NET_CURSOR_FORMAT_RGBA8_PREMULTIPLIED:
*type = CURSOR_TYPE_COLOR;
return true;
case LG_NET_CURSOR_FORMAT_MASKED_COLOR:
*type = CURSOR_TYPE_MASKED_COLOR;
return true;
case LG_NET_CURSOR_FORMAT_MONOCHROME:
*type = CURSOR_TYPE_MONOCHROME;
return true;
default:
return false;
}
}
static bool networkCursorPositionSupported(
const LGNetCursorPosition * position)
{
return position->displayID == 0 &&
!(position->flags & LG_NET_CURSOR_POSITION_RELATIVE) &&
position->x >= INT16_MIN && position->x <= INT16_MAX &&
position->y >= INT16_MIN && position->y <= INT16_MAX;
}
static bool networkCursorTransformSupported(
const LGNetCursorTransform * transform)
{
return transform->displayID == 0 &&
transform->rotation == LG_NET_CURSOR_ROTATION_0 && !transform->flags &&
transform->sourceWidth == transform->targetWidth &&
transform->sourceHeight == transform->targetHeight &&
transform->offsetX == 0 && transform->offsetY == 0 &&
transform->scaleNumerator == transform->scaleDenominator;
}
static bool networkCursorShapeCopy(NetworkCursor * cursor,
const LGNetCursorShape * shape)
{
KVMFRCursorType type;
const bool supported = networkCursorShapeType(shape->format, &type) &&
!(shape->flags & LG_NET_CURSOR_SHAPE_ANIMATED) &&
shape->hotspotX <= INT16_MAX && shape->hotspotY <= INT16_MAX;
uint8_t * data = malloc(shape->dataLength);
if (!data)
return false;
memcpy(data, shape->data, shape->dataLength);
if (shape->format == LG_NET_CURSOR_FORMAT_RGBA8_PREMULTIPLIED)
for (uint32_t y = 0; y < shape->height; ++y)
for (uint32_t x = 0; x < shape->width; ++x)
{
uint8_t * pixel = data + (size_t)y * shape->pitch + x * 4U;
const uint8_t red = pixel[0];
pixel[0] = pixel[2];
pixel[2] = red;
}
free(cursor->shapeData);
cursor->shape = *shape;
cursor->shapeData = data;
cursor->shape.data = data;
cursor->shapeValid = true;
cursor->shapeSupported = supported;
return true;
}
static bool networkCursorStateMissingNL(const NetworkCursor * cursor,
const LGNetCursorState * state, bool * impossible)
{
*impossible = false;
if (state->flags & LG_NET_CURSOR_STATE_SHAPE_VALID)
{
if (!cursor->shapeValid)
return true;
if (cursor->shape.shapeID != state->shapeID)
{
*impossible = cursor->shape.shapeID > state->shapeID;
return true;
}
}
if (state->flags & LG_NET_CURSOR_STATE_POSITION_VALID)
{
if (!cursor->positionValid)
return true;
if (cursor->position.updateID != state->positionID)
{
*impossible = cursor->position.updateID > state->positionID;
return true;
}
}
if (state->flags & LG_NET_CURSOR_STATE_TRANSFORM_VALID)
{
if (!cursor->transformValid)
return true;
if (cursor->transform.transformID != state->transformID)
{
*impossible = cursor->transform.transformID > state->transformID;
return true;
}
}
return false;
}
static struct NetworkCursorStatusReply networkCursorApplyStateNL(
NetworkCursor * cursor)
{
struct NetworkCursorStatusReply reply = { 0 };
if (!cursor->pendingStateValid)
return reply;
const LGNetCursorState state = cursor->pendingState;
bool impossible;
if (networkCursorStateMissingNL(cursor, &state, &impossible))
{
if (!impossible)
return reply;
reply.send = true;
reply.stateSequence = state.stateSequence;
reply.status = LG_NET_CURSOR_STATUS_INVALID;
cursor->pendingStateValid = false;
return reply;
}
const bool shapeValid =
state.flags & LG_NET_CURSOR_STATE_SHAPE_VALID;
const bool positionValid =
state.flags & LG_NET_CURSOR_STATE_POSITION_VALID;
const bool transformValid =
state.flags & LG_NET_CURSOR_STATE_TRANSFORM_VALID;
if (state.displayID != 0 ||
(shapeValid && !cursor->shapeSupported) ||
(positionValid && (!cursor->positionSupported ||
cursor->position.displayID != state.displayID)) ||
(transformValid && (!cursor->transformSupported ||
cursor->transform.displayID != state.displayID)))
{
reply.send = true;
reply.stateSequence = state.stateSequence;
reply.status = LG_NET_CURSOR_STATUS_UNSUPPORTED;
cursor->pendingStateValid = false;
return reply;
}
const bool newShape = shapeValid &&
cursor->appliedShapeID != state.shapeID;
const bool newPosition = positionValid &&
cursor->appliedPositionID != state.positionID;
const bool visible =
!!(state.flags & LG_NET_CURSOR_STATE_VISIBLE) &&
!(shapeValid &&
(cursor->shape.flags & LG_NET_CURSOR_SHAPE_HIDDEN));
const bool visibilityChanged =
!cursor->stateApplied || cursor->visible != visible;
struct NetworkCursorItem * item = NULL;
if (newShape || newPosition || visibilityChanged)
{
item = calloc(1, sizeof(*item));
if (!item)
goto capacity;
item->pointer.flags = LG_TRANSPORT_POINTER_VISIBLE_VALID;
item->pointer.sdrWhiteLevel = cursor->sdrWhiteLevel;
if (visible)
item->pointer.flags |= LG_TRANSPORT_POINTER_VISIBLE;
if (newPosition)
{
item->pointer.flags |= LG_TRANSPORT_POINTER_POSITION;
item->pointer.x = (int16_t)cursor->position.x;
item->pointer.y = (int16_t)cursor->position.y;
}
if (newShape)
{
if (!networkCursorShapeType(cursor->shape.format,
&item->pointer.type))
goto capacity;
item->shape = malloc(cursor->shape.dataLength);
if (!item->shape)
goto capacity;
memcpy(item->shape, cursor->shapeData, cursor->shape.dataLength);
item->pointer.flags |= LG_TRANSPORT_POINTER_SHAPE;
item->pointer.hx = (int16_t)cursor->shape.hotspotX;
item->pointer.hy = (int16_t)cursor->shape.hotspotY;
item->pointer.width = cursor->shape.width;
item->pointer.height = cursor->shape.height;
item->pointer.pitch = cursor->shape.pitch;
item->pointer.shape = item->shape;
}
if (!networkCursorQueueNL(cursor, item))
goto capacity;
item = NULL;
}
cursor->stateApplied = true;
cursor->appliedStateSequence = state.stateSequence;
cursor->appliedShapeID = shapeValid ? state.shapeID : 0;
cursor->appliedPositionID = positionValid ? state.positionID : 0;
cursor->appliedTransformID = transformValid ? state.transformID : 0;
cursor->visible = visible;
cursor->pendingStateValid = false;
reply.send = true;
reply.stateSequence = state.stateSequence;
reply.appliedShapeID = cursor->appliedShapeID;
reply.appliedPositionID = cursor->appliedPositionID;
reply.appliedTransformID = cursor->appliedTransformID;
reply.status = LG_NET_CURSOR_STATUS_APPLIED;
return reply;
capacity:
networkCursorItemFree(item);
cursor->pendingStateValid = false;
reply.send = true;
reply.stateSequence = state.stateSequence;
reply.status = LG_NET_CURSOR_STATUS_ERROR;
return reply;
}
static bool networkCursorSendStatus(NetworkCursor * cursor,
const struct NetworkCursorStatusReply * reply)
{
if (!reply->send)
return true;
uint16_t version;
LG_LOCK(cursor->lock);
const bool available = cursor->connected && cursor->available;
version = cursor->serviceVersion;
LG_UNLOCK(cursor->lock);
if (!available)
return false;
const LGNetCursorStatus status =
{
.stateSequence = reply->stateSequence,
.appliedShapeID = reply->appliedShapeID,
.appliedPositionID = reply->appliedPositionID,
.appliedTransformID = reply->appliedTransformID,
.status = reply->status,
.detail = reply->detail,
};
uint8_t payload[LG_NET_CURSOR_STATUS_WIRE_SIZE];
return lgNetCursorStatusEncode(payload, sizeof(payload), &status) &&
networkConnectionSend(cursor->connection, LG_NET_SERVICE_CURSOR,
LG_NET_CURSOR_MESSAGE_STATUS, version,
LG_NET_ENVELOPE_RESPONSE | LG_NET_ENVELOPE_FINAL,
0, status.stateSequence, payload, sizeof(payload)) == LG_TRANSPORT_OK;
}
bool networkCursorCreate(NetworkCursor ** result,
NetworkConnection * connection)
{
if (!result || !connection)
return false;
NetworkCursor * cursor = calloc(1, sizeof(*cursor));
if (!cursor)
return false;
LG_LOCK_INIT(cursor->lock);
cursor->event = lgCreateEvent(true, 0);
if (!cursor->event)
{
LG_LOCK_FREE(cursor->lock);
free(cursor);
return false;
}
cursor->connection = connection;
cursor->sdrWhiteLevel = LG_SDR_WHITE_LEVEL_DEFAULT;
*result = cursor;
return true;
}
void networkCursorDestroy(NetworkCursor ** result)
{
if (!result || !*result)
return;
NetworkCursor * cursor = *result;
LG_LOCK(cursor->lock);
networkCursorQueueClearNL(cursor);
networkCursorItemFree(cursor->outstanding);
cursor->outstanding = NULL;
networkCursorCacheClearNL(cursor);
LG_UNLOCK(cursor->lock);
lgSignalEvent(cursor->event);
lgFreeEvent(cursor->event);
LG_LOCK_FREE(cursor->lock);
free(cursor);
*result = NULL;
}
bool networkCursorConnectionState(NetworkCursor * cursor, bool connected)
{
uint16_t version = 0;
const bool available = connected &&
networkConnectionServiceAvailable(cursor->connection,
LG_NET_SERVICE_CURSOR, LG_NET_CAPABILITY_RELIABLE) &&
networkConnectionServiceVersion(cursor->connection,
LG_NET_SERVICE_CURSOR, &version);
LG_LOCK(cursor->lock);
cursor->connected = connected;
cursor->available = available;
cursor->cancelled = false;
cursor->serviceVersion = available ? version : 0;
networkCursorQueueClearNL(cursor);
networkCursorCacheClearNL(cursor);
LG_UNLOCK(cursor->lock);
lgSignalEvent(cursor->event);
return available;
}
static bool networkCursorEnvelope(const LGNetPacketView * packet,
bool datagram, uint64_t id)
{
const LGNetEnvelopeFlags expected = LG_NET_ENVELOPE_FINAL |
(datagram ? LG_NET_ENVELOPE_DATAGRAM : LG_NET_ENVELOPE_RELIABLE);
return packet->envelope.flags == expected &&
packet->envelope.componentEpoch == 0 &&
packet->envelope.requestID == id;
}
static bool networkCursorPositionPacket(NetworkCursor * cursor,
const LGNetPacketView * packet, bool datagram)
{
LGNetCursorPosition position;
if (lgNetCursorPositionDecode(&position,
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
!networkCursorEnvelope(packet, datagram, position.updateID))
return false;
struct NetworkCursorStatusReply reply = { 0 };
LG_LOCK(cursor->lock);
const bool valid = cursor->connected && cursor->available;
if (valid && (!cursor->positionValid ||
position.updateID > cursor->position.updateID))
{
cursor->position = position;
cursor->positionValid = true;
cursor->positionSupported = networkCursorPositionSupported(&position);
reply = networkCursorApplyStateNL(cursor);
}
LG_UNLOCK(cursor->lock);
return valid && networkCursorSendStatus(cursor, &reply);
}
static bool networkCursorShapePacket(NetworkCursor * cursor,
const LGNetPacketView * packet, bool datagram)
{
if (datagram)
return false;
LGNetCursorShape shape;
if (lgNetCursorShapeDecode(&shape,
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
!networkCursorEnvelope(packet, false, shape.shapeID))
return false;
struct NetworkCursorStatusReply reply = { 0 };
LG_LOCK(cursor->lock);
const bool valid = cursor->connected && cursor->available;
bool copied = true;
if (valid && (!cursor->shapeValid || shape.shapeID > cursor->shape.shapeID))
{
copied = networkCursorShapeCopy(cursor, &shape);
if (copied)
reply = networkCursorApplyStateNL(cursor);
}
LG_UNLOCK(cursor->lock);
return valid && copied && networkCursorSendStatus(cursor, &reply);
}
static bool networkCursorTransformPacket(NetworkCursor * cursor,
const LGNetPacketView * packet, bool datagram)
{
if (datagram)
return false;
LGNetCursorTransform transform;
if (lgNetCursorTransformDecode(&transform,
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
!networkCursorEnvelope(packet, false, transform.transformID))
return false;
struct NetworkCursorStatusReply reply = { 0 };
LG_LOCK(cursor->lock);
const bool valid = cursor->connected && cursor->available;
if (valid && (!cursor->transformValid ||
transform.transformID > cursor->transform.transformID))
{
cursor->transform = transform;
cursor->transformValid = true;
cursor->transformSupported =
networkCursorTransformSupported(&transform);
reply = networkCursorApplyStateNL(cursor);
}
LG_UNLOCK(cursor->lock);
return valid && networkCursorSendStatus(cursor, &reply);
}
static bool networkCursorStatePacket(NetworkCursor * cursor,
const LGNetPacketView * packet, bool datagram)
{
if (datagram)
return false;
LGNetCursorState state;
if (lgNetCursorStateDecode(&state,
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
!networkCursorEnvelope(packet, false, state.stateSequence))
return false;
struct NetworkCursorStatusReply reply = { 0 };
LG_LOCK(cursor->lock);
const bool valid = cursor->connected && cursor->available;
if (valid && state.stateSequence > cursor->appliedStateSequence &&
(!cursor->pendingStateValid ||
state.stateSequence > cursor->pendingState.stateSequence))
{
cursor->pendingState = state;
cursor->pendingStateValid = true;
reply = networkCursorApplyStateNL(cursor);
}
LG_UNLOCK(cursor->lock);
return valid && networkCursorSendStatus(cursor, &reply);
}
static bool networkCursorColorPacket(NetworkCursor * cursor,
const LGNetPacketView * packet, bool datagram)
{
if (datagram)
return false;
LGNetCursorColorTransform transform;
if (lgNetCursorColorTransformDecode(&transform,
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
!networkCursorEnvelope(packet, false, transform.updateID))
return false;
struct NetworkCursorItem * item = calloc(1, sizeof(*item));
if (!item)
return false;
item->colorTransform = calloc(1, sizeof(*item->colorTransform));
if (!item->colorTransform)
{
networkCursorItemFree(item);
return false;
}
if (transform.flags & LG_NET_CURSOR_COLOR_TRANSFORM_MATRIX)
item->colorTransform->flags |= LG_COLOR_TRANSFORM_MATRIX;
if (transform.flags & LG_NET_CURSOR_COLOR_TRANSFORM_LUT)
item->colorTransform->flags |= LG_COLOR_TRANSFORM_LUT;
memcpy(item->colorTransform->matrix, transform.matrix,
sizeof(transform.matrix));
item->colorTransform->scalar = transform.scalar;
if ((transform.flags & LG_NET_CURSOR_COLOR_TRANSFORM_LUT) &&
!lgNetCursorColorLUTDecode((float *)item->colorTransform->lut,
transform.lut, LG_NET_CURSOR_COLOR_LUT_BYTES))
{
networkCursorItemFree(item);
return false;
}
item->pointer.flags = LG_TRANSPORT_POINTER_COLOR_TRANSFORM;
item->pointer.sdrWhiteLevel = transform.sdrWhiteLevel;
item->pointer.colorTransform = item->colorTransform;
LG_LOCK(cursor->lock);
const bool valid = cursor->connected && cursor->available;
bool queued = true;
if (valid && transform.updateID > cursor->colorUpdateID)
{
cursor->colorUpdateID = transform.updateID;
cursor->sdrWhiteLevel = transform.sdrWhiteLevel;
if (cursor->stateApplied)
{
item->pointer.flags |= LG_TRANSPORT_POINTER_VISIBLE_VALID;
if (cursor->visible)
item->pointer.flags |= LG_TRANSPORT_POINTER_VISIBLE;
}
queued = networkCursorQueueNL(cursor, item);
if (queued)
item = NULL;
}
LG_UNLOCK(cursor->lock);
networkCursorItemFree(item);
return valid && queued;
}
static bool networkCursorStatusPacket(const LGNetPacketView * packet,
bool datagram)
{
if (datagram)
return false;
LGNetCursorStatus status;
if (lgNetCursorStatusDecode(&status,
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
packet->envelope.componentEpoch != 0 ||
packet->envelope.requestID != status.stateSequence)
return false;
const LGNetEnvelopeFlags base =
LG_NET_ENVELOPE_FINAL | LG_NET_ENVELOPE_RELIABLE;
return packet->envelope.flags == base ||
packet->envelope.flags == (base | LG_NET_ENVELOPE_RESPONSE);
}
bool networkCursorPacket(NetworkCursor * cursor,
const LGNetPacketView * packet, bool datagram)
{
if (!cursor || !packet ||
packet->envelope.service != LG_NET_SERVICE_CURSOR)
return false;
switch (packet->envelope.messageType)
{
case LG_NET_CURSOR_MESSAGE_STATE:
return networkCursorStatePacket(cursor, packet, datagram);
case LG_NET_CURSOR_MESSAGE_POSITION:
return networkCursorPositionPacket(cursor, packet, datagram);
case LG_NET_CURSOR_MESSAGE_SHAPE:
return networkCursorShapePacket(cursor, packet, datagram);
case LG_NET_CURSOR_MESSAGE_TRANSFORM:
return networkCursorTransformPacket(cursor, packet, datagram);
case LG_NET_CURSOR_MESSAGE_STATUS:
return networkCursorStatusPacket(packet, datagram);
case LG_NET_CURSOR_MESSAGE_COLOR_TRANSFORM:
return networkCursorColorPacket(cursor, packet, datagram);
default:
return false;
}
}
LG_TransportStatus networkCursorNext(NetworkCursor * cursor,
LG_TransportPointer * pointer)
{
if (!cursor || !pointer)
return LG_TRANSPORT_ERROR;
for (;;)
{
LG_LOCK(cursor->lock);
if (!cursor->connected)
{
LG_UNLOCK(cursor->lock);
return LG_TRANSPORT_DISCONNECTED;
}
if (!cursor->available)
{
LG_UNLOCK(cursor->lock);
return LG_TRANSPORT_UNAVAILABLE;
}
if (cursor->cancelled)
{
cursor->cancelled = false;
LG_UNLOCK(cursor->lock);
return LG_TRANSPORT_TIMEOUT;
}
if (cursor->outstanding)
{
LG_UNLOCK(cursor->lock);
return LG_TRANSPORT_ERROR;
}
if (cursor->head)
{
struct NetworkCursorItem * item = cursor->head;
cursor->head = item->next;
if (!cursor->head)
cursor->tail = NULL;
--cursor->count;
item->next = NULL;
cursor->outstanding = item;
*pointer = item->pointer;
if (cursor->head)
lgSignalEvent(cursor->event);
LG_UNLOCK(cursor->lock);
return LG_TRANSPORT_OK;
}
LG_UNLOCK(cursor->lock);
lgWaitEvent(cursor->event, TIMEOUT_INFINITE);
}
}
void networkCursorRelease(NetworkCursor * cursor,
LG_TransportPointer * pointer)
{
if (!cursor || !pointer)
return;
LG_LOCK(cursor->lock);
struct NetworkCursorItem * item = cursor->outstanding;
cursor->outstanding = NULL;
LG_UNLOCK(cursor->lock);
networkCursorItemFree(item);
memset(pointer, 0, sizeof(*pointer));
}
void networkCursorCancel(NetworkCursor * cursor)
{
if (!cursor)
return;
LG_LOCK(cursor->lock);
cursor->cancelled = true;
LG_UNLOCK(cursor->lock);
lgSignalEvent(cursor->event);
}
void networkCursorStop(NetworkCursor * cursor)
{
networkCursorCancel(cursor);
}

View File

@@ -0,0 +1,46 @@
/**
* 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
*/
#pragma once
#include "connection.h"
#include "interface/transport.h"
#include <LGProtocol/NetworkProtocol.h>
#include <stdbool.h>
typedef struct NetworkCursor NetworkCursor;
bool networkCursorCreate(NetworkCursor ** cursor,
NetworkConnection * connection);
void networkCursorDestroy(NetworkCursor ** cursor);
bool networkCursorConnectionState(NetworkCursor * cursor, bool connected);
bool networkCursorPacket(NetworkCursor * cursor,
const LGNetPacketView * packet, bool datagram);
LG_TransportStatus networkCursorNext(NetworkCursor * cursor,
LG_TransportPointer * pointer);
void networkCursorRelease(NetworkCursor * cursor,
LG_TransportPointer * pointer);
void networkCursorCancel(NetworkCursor * cursor);
void networkCursorStop(NetworkCursor * cursor);

View File

@@ -20,8 +20,11 @@
#include "network.h" #include "network.h"
#include "connection.h" #include "connection.h"
#include "control.h"
#include "cursor.h"
#include "input.h" #include "input.h"
#include "known_hosts.h" #include "known_hosts.h"
#include "recovery.h"
#include "app.h" #include "app.h"
#include "interface/renderer.h" #include "interface/renderer.h"
@@ -30,6 +33,7 @@
#include "common/event.h" #include "common/event.h"
#include "common/locking.h" #include "common/locking.h"
#include <LGProtocol/NetworkProtocol.h> #include <LGProtocol/NetworkProtocol.h>
#include <LGProtocol/NetworkServices.h>
#include "common/option.h" #include "common/option.h"
#include <stddef.h> #include <stddef.h>
@@ -40,11 +44,35 @@
#define NETWORK_ADDRESS_MAX 255U #define NETWORK_ADDRESS_MAX 255U
#define NETWORK_ENDPOINT_TEXT_SIZE (NETWORK_ADDRESS_MAX + 9U) #define NETWORK_ENDPOINT_TEXT_SIZE (NETWORK_ADDRESS_MAX + 9U)
#define NETWORK_MAX_PAYLOAD (1024U * 1024U) #define NETWORK_MAX_PAYLOAD \
(LG_NET_CURSOR_SHAPE_HEADER_WIRE_SIZE + LG_NET_CURSOR_MAX_SHAPE_LENGTH)
#define NETWORK_MAX_DATAGRAM 1136U #define NETWORK_MAX_DATAGRAM 1136U
static const LGNetCapability NETWORK_CAPABILITIES[] = static const LGNetCapability NETWORK_CAPABILITIES[] =
{ {
{
.service = LG_NET_SERVICE_RECOVERY,
.versionMin = LG_NET_RECOVERY_VERSION_MIN,
.versionMax = LG_NET_RECOVERY_VERSION_CURRENT,
.flags = LG_NET_CAPABILITY_RELIABLE,
.features = 0,
.maxPayloadLength = NETWORK_MAX_PAYLOAD,
.maxDatagramPayload = 0,
.maxBidirectionalStreams = 0,
.maxUnidirectionalStreams = 0,
},
{
.service = LG_NET_SERVICE_CURSOR,
.versionMin = LG_NET_CURSOR_VERSION_MIN,
.versionMax = LG_NET_CURSOR_VERSION_CURRENT,
.flags = LG_NET_CAPABILITY_RELIABLE |
LG_NET_CAPABILITY_DATAGRAM,
.features = 0,
.maxPayloadLength = NETWORK_MAX_PAYLOAD,
.maxDatagramPayload = NETWORK_MAX_DATAGRAM,
.maxBidirectionalStreams = 0,
.maxUnidirectionalStreams = 0,
},
{ {
.service = LG_NET_SERVICE_INPUT, .service = LG_NET_SERVICE_INPUT,
.versionMin = LG_NET_INPUT_VERSION_MIN, .versionMin = LG_NET_INPUT_VERSION_MIN,
@@ -57,6 +85,17 @@ static const LGNetCapability NETWORK_CAPABILITIES[] =
.maxBidirectionalStreams = 0, .maxBidirectionalStreams = 0,
.maxUnidirectionalStreams = 0, .maxUnidirectionalStreams = 0,
}, },
{
.service = LG_NET_SERVICE_CONTROL,
.versionMin = LG_NET_CONTROL_VERSION_MIN,
.versionMax = LG_NET_CONTROL_VERSION_CURRENT,
.flags = LG_NET_CAPABILITY_RELIABLE,
.features = 0,
.maxPayloadLength = NETWORK_MAX_PAYLOAD,
.maxDatagramPayload = 0,
.maxBidirectionalStreams = 0,
.maxUnidirectionalStreams = 0,
},
}; };
struct NetworkTrustPrompt struct NetworkTrustPrompt
@@ -69,7 +108,10 @@ struct NetworkTrustPrompt
struct LG_Transport struct LG_Transport
{ {
NetworkConnection * connection; NetworkConnection * connection;
NetworkControl * control;
NetworkCursor * cursor;
NetworkInput * input; NetworkInput * input;
NetworkRecovery * recovery;
NetworkKnownHosts knownHosts; NetworkKnownHosts knownHosts;
LGEvent * frameEvent; LGEvent * frameEvent;
LGEvent * promptIdle; LGEvent * promptIdle;
@@ -265,6 +307,14 @@ static void networkSetUnavailable(LG_VideoComponentStatus * status,
status->reason = reason; status->reason = reason;
} }
static void networkSetAvailable(LG_VideoComponentStatus * status)
{
if (!status->available)
status->epoch = networkNextEpoch(status->epoch);
status->available = true;
status->reason = LG_TRANSPORT_OK;
}
static void networkConnectionState(void * opaque, bool connected, static void networkConnectionState(void * opaque, bool connected,
LG_TransportStatus reason) LG_TransportStatus reason)
{ {
@@ -273,10 +323,17 @@ static void networkConnectionState(void * opaque, bool connected,
LG_TRANSPORT_UNAVAILABLE : reason; LG_TRANSPORT_UNAVAILABLE : reason;
networkInputConnectionState(this->input, connected); networkInputConnectionState(this->input, connected);
networkControlConnectionState(this->control, connected);
networkRecoveryConnectionState(this->recovery, connected);
const bool cursorAvailable =
networkCursorConnectionState(this->cursor, connected);
LG_LOCK(this->stateLock); LG_LOCK(this->stateLock);
networkSetUnavailable(&this->videoStatus.frame, status); networkSetUnavailable(&this->videoStatus.frame, status);
networkSetUnavailable(&this->videoStatus.pointer, status); if (cursorAvailable)
networkSetAvailable(&this->videoStatus.pointer);
else
networkSetUnavailable(&this->videoStatus.pointer, status);
LG_UNLOCK(this->stateLock); LG_UNLOCK(this->stateLock);
lgSignalEvent(this->frameEvent); lgSignalEvent(this->frameEvent);
@@ -287,10 +344,31 @@ static void networkPacket(void * opaque, const LGNetPacketView * packet,
bool datagram) bool datagram)
{ {
LG_Transport * this = opaque; LG_Transport * this = opaque;
if (packet->envelope.service == LG_NET_SERVICE_INPUT && bool valid;
!networkInputPacket(this->input, packet, datagram)) switch (packet->envelope.service)
{ {
DEBUG_ERROR("Invalid network input message"); case LG_NET_SERVICE_RECOVERY:
valid = networkRecoveryPacket(this->recovery, packet, datagram);
break;
case LG_NET_SERVICE_CURSOR:
valid = networkCursorPacket(this->cursor, packet, datagram);
break;
case LG_NET_SERVICE_INPUT:
valid = networkInputPacket(this->input, packet, datagram);
break;
case LG_NET_SERVICE_CONTROL:
valid = networkControlPacket(this->control, packet, datagram);
break;
default:
valid = false;
break;
}
if (!valid)
{
DEBUG_ERROR("Invalid network service %u message %u",
(unsigned)packet->envelope.service,
(unsigned)packet->envelope.messageType);
networkConnectionCancel(this->connection); networkConnectionCancel(this->connection);
} }
} }
@@ -351,14 +429,20 @@ static bool networkCreate(LG_Transport ** result)
}; };
if (!networkConnectionCreate(&this->connection, &params)) if (!networkConnectionCreate(&this->connection, &params))
goto fail; goto fail;
if (!networkInputCreate(&this->input, this->connection)) if (!networkControlCreate(&this->control, this->connection) ||
!networkCursorCreate(&this->cursor, this->connection) ||
!networkInputCreate(&this->input, this->connection) ||
!networkRecoveryCreate(&this->recovery, this->connection))
goto fail; goto fail;
*result = this; *result = this;
return true; return true;
fail: fail:
networkRecoveryDestroy(&this->recovery);
networkInputDestroy(&this->input); networkInputDestroy(&this->input);
networkCursorDestroy(&this->cursor);
networkControlDestroy(&this->control);
networkConnectionDestroy(&this->connection); networkConnectionDestroy(&this->connection);
if (this->frameEvent) if (this->frameEvent)
lgFreeEvent(this->frameEvent); lgFreeEvent(this->frameEvent);
@@ -389,7 +473,10 @@ static void networkDestroy(LG_Transport ** transport)
LG_UNLOCK(this->stateLock); LG_UNLOCK(this->stateLock);
LG_UNLOCK(this->callbackLock); LG_UNLOCK(this->callbackLock);
networkRecoveryDestroy(&this->recovery);
networkInputDestroy(&this->input); networkInputDestroy(&this->input);
networkCursorDestroy(&this->cursor);
networkControlDestroy(&this->control);
networkConnectionDestroy(&this->connection); networkConnectionDestroy(&this->connection);
lgFreeEvent(this->frameEvent); lgFreeEvent(this->frameEvent);
lgFreeEvent(this->promptIdle); lgFreeEvent(this->promptIdle);
@@ -418,6 +505,9 @@ static LG_TransportStatus networkConnect(LG_Transport * this,
static void networkDisconnect(LG_Transport * this) static void networkDisconnect(LG_Transport * this)
{ {
networkInputConnectionState(this->input, false); networkInputConnectionState(this->input, false);
networkControlConnectionState(this->control, false);
networkRecoveryConnectionState(this->recovery, false);
networkCursorConnectionState(this->cursor, false);
networkConnectionDisconnect(this->connection); networkConnectionDisconnect(this->connection);
lgSignalEvent(this->frameEvent); lgSignalEvent(this->frameEvent);
} }
@@ -516,6 +606,36 @@ static void networkStopFrame(LG_Transport * this)
lgSignalEvent(this->frameEvent); lgSignalEvent(this->frameEvent);
} }
static LG_TransportStatus networkNextPointer(LG_Transport * this,
LG_TransportPointer * pointer)
{
const LG_TransportStatus status =
networkCursorNext(this->cursor, pointer);
if (status != LG_TRANSPORT_OK)
return status;
LG_LOCK(this->stateLock);
pointer->epoch = this->videoStatus.pointer.epoch;
LG_UNLOCK(this->stateLock);
return LG_TRANSPORT_OK;
}
static void networkReleasePointer(LG_Transport * this,
LG_TransportPointer * pointer)
{
networkCursorRelease(this->cursor, pointer);
}
static void networkCancelPointerWait(LG_Transport * this)
{
networkCursorCancel(this->cursor);
}
static void networkStopPointer(LG_Transport * this)
{
networkCursorStop(this->cursor);
}
static const LG_FrameOps networkFrameOps = static const LG_FrameOps networkFrameOps =
{ {
.setStatusListener = networkSetStatusListener, .setStatusListener = networkSetStatusListener,
@@ -527,6 +647,10 @@ static const LG_FrameOps networkFrameOps =
.releaseFrame = networkReleaseFrame, .releaseFrame = networkReleaseFrame,
.cancelFrameWait = networkCancelFrameWait, .cancelFrameWait = networkCancelFrameWait,
.stopFrame = networkStopFrame, .stopFrame = networkStopFrame,
.nextPointer = networkNextPointer,
.releasePointer = networkReleasePointer,
.cancelPointerWait = networkCancelPointerWait,
.stopPointer = networkStopPointer,
}; };
static const LG_VideoOps networkVideoOps = static const LG_VideoOps networkVideoOps =
@@ -545,6 +669,10 @@ static const LG_VideoOps * networkGetVideoOps(LG_Transport * this)
static const LG_InputOps * networkGetInputOps(LG_Transport * this, static const LG_InputOps * networkGetInputOps(LG_Transport * this,
void ** opaque) void ** opaque)
{ {
*opaque = NULL;
if (!networkConnectionServiceAvailable(this->connection,
LG_NET_SERVICE_INPUT, LG_NET_CAPABILITY_RELIABLE))
return NULL;
*opaque = this->input; *opaque = this->input;
return networkInputGetOps(); return networkInputGetOps();
} }
@@ -568,18 +696,26 @@ static const LG_ClipboardOps * networkGetClipboardOps(LG_Transport * this,
static LG_TransportStatus networkSendControl(LG_Transport * this, static LG_TransportStatus networkSendControl(LG_Transport * this,
const LG_TransportControl * control, LG_TransportControlToken * token) const LG_TransportControl * control, LG_TransportControlToken * token)
{ {
(void)control; return networkControlSend(this->control, control, token);
(void)token;
return networkSessionValid(this) ?
LG_TRANSPORT_UNAVAILABLE : LG_TRANSPORT_DISCONNECTED;
} }
static LG_TransportStatus networkControlStatus(LG_Transport * this, static LG_TransportStatus networkControlStatusOp(LG_Transport * this,
LG_TransportControlToken token) LG_TransportControlToken token)
{ {
(void)token; return networkControlStatus(this->control, token);
return networkSessionValid(this) ? }
LG_TRANSPORT_UNAVAILABLE : LG_TRANSPORT_DISCONNECTED;
static LG_TransportStatus networkGetRecoveryInfo(LG_Transport * this,
LG_RecoveryInfo * info)
{
return networkRecoveryGetInfo(this->recovery, info);
}
static LG_TransportStatus networkRequestRecovery(LG_Transport * this,
LG_RecoveryRequest request, uint64_t * instance, uint32_t * serial)
{
return networkRecoveryRequest(
this->recovery, request, instance, serial);
} }
const LG_TransportOps LGT_Network = const LG_TransportOps LGT_Network =
@@ -596,6 +732,8 @@ const LG_TransportOps LGT_Network =
.getInputOps = networkGetInputOps, .getInputOps = networkGetInputOps,
.getAudioOps = networkGetAudioOps, .getAudioOps = networkGetAudioOps,
.getClipboardOps = networkGetClipboardOps, .getClipboardOps = networkGetClipboardOps,
.getRecoveryInfo = networkGetRecoveryInfo,
.requestRecovery = networkRequestRecovery,
.sendControl = networkSendControl, .sendControl = networkSendControl,
.controlStatus = networkControlStatus, .controlStatus = networkControlStatusOp,
}; };

View File

@@ -0,0 +1,575 @@
/**
* 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 "recovery.h"
#include "common/event.h"
#include "common/locking.h"
#include <LGProtocol/NetworkServices.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#define NETWORK_RECOVERY_QUERY_TIMEOUT_MS 500U
#define NETWORK_RECOVERY_DEFAULT_TIMEOUT_MS 30000U
#define NETWORK_RECOVERY_REQUEST_CAPACITY 16U
struct NetworkRecoveryRequestEntry
{
uint64_t id;
uint64_t session;
uint32_t serial;
LG_RecoveryRequest request;
bool acknowledged;
};
struct NetworkRecovery
{
NetworkConnection * connection;
LGEvent * queryEvent;
LG_Lock lock;
bool connected;
bool available;
uint16_t serviceVersion;
uint64_t nextID;
uint32_t nextSerial;
bool queryPending;
uint64_t queryID;
bool infoValid;
LGNetRecoveryInfo info;
char version[LG_NET_RECOVERY_MAX_VERSION_LENGTH + 1U];
uint32_t requestSerial;
LG_RecoveryRequest request;
uint32_t ackSerial;
LG_RecoveryRequest ackRequest;
struct NetworkRecoveryRequestEntry requests[NETWORK_RECOVERY_REQUEST_CAPACITY];
unsigned nextRequest;
};
static uint64_t networkRecoveryNextID(uint64_t id)
{
++id;
return id ? id : 1U;
}
static uint32_t networkRecoveryNextSerial(uint32_t serial)
{
++serial;
return serial ? serial : 1U;
}
static bool networkRecoverySequenceNewer(uint64_t sequence,
uint64_t reference)
{
return sequence > reference;
}
static struct NetworkRecoveryRequestEntry * networkRecoveryFindRequestNL(
NetworkRecovery * recovery, uint64_t id)
{
for (size_t index = 0;
index < NETWORK_RECOVERY_REQUEST_CAPACITY; ++index)
if (recovery->requests[index].id == id)
return &recovery->requests[index];
return NULL;
}
static struct NetworkRecoveryRequestEntry * networkRecoveryReserveRequestNL(
NetworkRecovery * recovery)
{
for (size_t offset = 0;
offset < NETWORK_RECOVERY_REQUEST_CAPACITY; ++offset)
{
const unsigned index =
(recovery->nextRequest + offset) % NETWORK_RECOVERY_REQUEST_CAPACITY;
struct NetworkRecoveryRequestEntry * entry = &recovery->requests[index];
if (entry->id && !entry->acknowledged)
continue;
recovery->nextRequest =
(index + 1U) % NETWORK_RECOVERY_REQUEST_CAPACITY;
return entry;
}
return NULL;
}
static bool networkRecoveryState(
LGNetRecoveryState state, LG_RecoveryState * result)
{
switch (state)
{
case LG_NET_RECOVERY_STATE_UNKNOWN:
*result = LG_RECOVERY_STATE_UNKNOWN;
return true;
case LG_NET_RECOVERY_STATE_NORMAL:
*result = LG_RECOVERY_STATE_NORMAL;
return true;
case LG_NET_RECOVERY_STATE_SWITCHING:
*result = LG_RECOVERY_STATE_SWITCHING;
return true;
case LG_NET_RECOVERY_STATE_ACTIVE:
*result = LG_RECOVERY_STATE_ACTIVE;
return true;
case LG_NET_RECOVERY_STATE_FAILED:
*result = LG_RECOVERY_STATE_FAILED;
return true;
default:
return false;
}
}
static bool networkRecoveryError(
LGNetRecoveryError error, LG_RecoveryError * result)
{
switch (error)
{
case LG_NET_RECOVERY_ERROR_NONE:
*result = LG_RECOVERY_ERR_NONE;
return true;
case LG_NET_RECOVERY_ERROR_UNSUPPORTED:
*result = LG_RECOVERY_ERR_UNSUPPORTED;
return true;
case LG_NET_RECOVERY_ERROR_HELPER_UNAVAILABLE:
*result = LG_RECOVERY_ERR_HELPER_UNAVAILABLE;
return true;
case LG_NET_RECOVERY_ERROR_TOPOLOGY_FAILED:
*result = LG_RECOVERY_ERR_TOPOLOGY_FAILED;
return true;
case LG_NET_RECOVERY_ERROR_NO_FALLBACK_DISPLAY:
*result = LG_RECOVERY_ERR_NO_FALLBACK_DISPLAY;
return true;
case LG_NET_RECOVERY_ERROR_BUSY:
*result = LG_RECOVERY_ERR_BUSY;
return true;
case LG_NET_RECOVERY_ERROR_CAPACITY:
*result = LG_RECOVERY_ERR_CAPACITY;
return true;
case LG_NET_RECOVERY_ERROR_TIMEOUT:
/* The public transport ABI has no timeout-specific recovery error. */
*result = LG_RECOVERY_ERR_HELPER_UNAVAILABLE;
return true;
default:
return false;
}
}
static void networkRecoveryClearNL(NetworkRecovery * recovery)
{
recovery->queryPending = false;
recovery->queryID = 0;
recovery->infoValid = false;
recovery->info = (LGNetRecoveryInfo) { 0 };
recovery->version[0] = '\0';
recovery->requestSerial = 0;
recovery->request = LG_RECOVERY_REQ_NONE;
recovery->ackSerial = 0;
recovery->ackRequest = LG_RECOVERY_REQ_NONE;
recovery->nextRequest = 0;
memset(recovery->requests, 0, sizeof(recovery->requests));
}
static bool networkRecoveryBeginQuery(NetworkRecovery * recovery,
uint64_t * queryID, uint16_t * version)
{
LG_LOCK(recovery->lock);
if (!recovery->connected || !recovery->available)
{
LG_UNLOCK(recovery->lock);
return false;
}
if (recovery->queryPending)
{
*queryID = recovery->queryID;
*version = recovery->serviceVersion;
LG_UNLOCK(recovery->lock);
return true;
}
recovery->nextID = networkRecoveryNextID(recovery->nextID);
recovery->queryID = recovery->nextID;
recovery->queryPending = true;
*queryID = recovery->queryID;
*version = recovery->serviceVersion;
lgResetEvent(recovery->queryEvent);
LG_UNLOCK(recovery->lock);
const LG_TransportStatus status = networkConnectionSend(
recovery->connection, LG_NET_SERVICE_RECOVERY,
LG_NET_RECOVERY_MESSAGE_GET_INFO, *version,
LG_NET_ENVELOPE_ACK_REQUIRED | LG_NET_ENVELOPE_FINAL,
0, *queryID, NULL, 0);
if (status == LG_TRANSPORT_OK)
return true;
LG_LOCK(recovery->lock);
if (recovery->queryID == *queryID)
{
recovery->queryPending = false;
recovery->queryID = 0;
}
LG_UNLOCK(recovery->lock);
lgSignalEvent(recovery->queryEvent);
return false;
}
bool networkRecoveryCreate(NetworkRecovery ** result,
NetworkConnection * connection)
{
if (!result || !connection)
return false;
NetworkRecovery * recovery = calloc(1, sizeof(*recovery));
if (!recovery)
return false;
LG_LOCK_INIT(recovery->lock);
recovery->queryEvent = lgCreateEvent(false, 0);
if (!recovery->queryEvent)
{
LG_LOCK_FREE(recovery->lock);
free(recovery);
return false;
}
recovery->connection = connection;
*result = recovery;
return true;
}
void networkRecoveryDestroy(NetworkRecovery ** result)
{
if (!result || !*result)
return;
NetworkRecovery * recovery = *result;
lgSignalEvent(recovery->queryEvent);
lgFreeEvent(recovery->queryEvent);
LG_LOCK_FREE(recovery->lock);
free(recovery);
*result = NULL;
}
void networkRecoveryConnectionState(NetworkRecovery * recovery,
bool connected)
{
uint16_t version = 0;
const bool available = connected &&
networkConnectionServiceAvailable(recovery->connection,
LG_NET_SERVICE_RECOVERY, LG_NET_CAPABILITY_RELIABLE) &&
networkConnectionServiceVersion(recovery->connection,
LG_NET_SERVICE_RECOVERY, &version);
LG_LOCK(recovery->lock);
recovery->connected = connected;
recovery->available = available;
recovery->serviceVersion = available ? version : 0;
networkRecoveryClearNL(recovery);
LG_UNLOCK(recovery->lock);
lgSignalEvent(recovery->queryEvent);
if (available)
{
uint64_t queryID;
networkRecoveryBeginQuery(recovery, &queryID, &version);
}
}
static bool networkRecoveryInfoPacket(NetworkRecovery * recovery,
const LGNetPacketView * packet)
{
const LGNetEnvelopeFlags expected =
LG_NET_ENVELOPE_RESPONSE |
LG_NET_ENVELOPE_FINAL |
LG_NET_ENVELOPE_RELIABLE;
if (packet->envelope.flags != expected ||
packet->envelope.componentEpoch != 0 ||
!packet->envelope.requestID)
return false;
LGNetRecoveryInfo info;
if (lgNetRecoveryInfoDecode(&info,
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK)
return false;
char version[LG_NET_RECOVERY_MAX_VERSION_LENGTH + 1U];
if (info.versionLength)
memcpy(version, info.version, info.versionLength);
version[info.versionLength] = '\0';
info.version = NULL;
LG_LOCK(recovery->lock);
const bool valid = recovery->connected && recovery->available &&
recovery->queryPending &&
recovery->queryID == packet->envelope.requestID;
if (valid)
{
if (!recovery->infoValid ||
!networkRecoverySequenceNewer(
recovery->info.statusSequence, info.statusSequence))
{
recovery->info = info;
memcpy(recovery->version, version, info.versionLength + 1U);
}
recovery->infoValid = true;
recovery->queryPending = false;
recovery->queryID = 0;
}
LG_UNLOCK(recovery->lock);
if (valid)
lgSignalEvent(recovery->queryEvent);
return valid;
}
static bool networkRecoveryStatusPacket(NetworkRecovery * recovery,
const LGNetPacketView * packet)
{
LGNetRecoveryStatus status;
if (lgNetRecoveryStatusDecode(&status,
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
status.requestID != packet->envelope.requestID ||
packet->envelope.componentEpoch != 0)
return false;
const bool terminal =
status.state == LG_NET_RECOVERY_STATE_NORMAL ||
status.state == LG_NET_RECOVERY_STATE_ACTIVE ||
status.state == LG_NET_RECOVERY_STATE_FAILED;
const LGNetEnvelopeFlags expected = LG_NET_ENVELOPE_RELIABLE |
(status.requestID ? LG_NET_ENVELOPE_RESPONSE : 0) |
(terminal ? LG_NET_ENVELOPE_FINAL : 0);
if (packet->envelope.flags != expected)
return false;
LG_LOCK(recovery->lock);
struct NetworkRecoveryRequestEntry * request = status.requestID ?
networkRecoveryFindRequestNL(recovery, status.requestID) : NULL;
const bool valid =
recovery->connected && recovery->available &&
recovery->infoValid && status.sessionID == recovery->info.sessionID &&
(!status.requestID ||
(request && request->session == status.sessionID));
if (valid && (!networkRecoverySequenceNewer(
recovery->info.statusSequence, status.statusSequence)))
{
recovery->info.statusSequence = status.statusSequence;
recovery->info.state = status.state;
recovery->info.error = status.error;
recovery->info.flags = status.flags;
if (request && terminal)
{
request->acknowledged = true;
recovery->ackSerial = request->serial;
recovery->ackRequest = request->request;
}
}
LG_UNLOCK(recovery->lock);
return valid;
}
bool networkRecoveryPacket(NetworkRecovery * recovery,
const LGNetPacketView * packet, bool datagram)
{
if (!recovery || !packet || datagram ||
packet->envelope.service != LG_NET_SERVICE_RECOVERY)
return false;
switch (packet->envelope.messageType)
{
case LG_NET_RECOVERY_MESSAGE_INFO:
return networkRecoveryInfoPacket(recovery, packet);
case LG_NET_RECOVERY_MESSAGE_STATUS:
return networkRecoveryStatusPacket(recovery, packet);
default:
return false;
}
}
static bool networkRecoveryCopyInfoNL(const NetworkRecovery * recovery,
LG_RecoveryInfo * result)
{
LG_RecoveryState state;
LG_RecoveryError error;
if (!recovery->infoValid ||
!networkRecoveryState(recovery->info.state, &state) ||
!networkRecoveryError(recovery->info.error, &error))
return false;
memset(result, 0, sizeof(*result));
result->abiVersion = recovery->serviceVersion;
if (recovery->info.capabilities & LG_NET_RECOVERY_CAP_DISPLAY)
result->capabilities |= LG_RECOVERY_CAP_DISPLAY;
result->instance = recovery->info.sessionID;
result->heartbeat = (uint32_t)recovery->info.statusSequence;
const size_t versionLength = recovery->info.versionLength <
sizeof(result->producerVersion) - 1U ?
recovery->info.versionLength : sizeof(result->producerVersion) - 1U;
memcpy(result->producerVersion, recovery->version, versionLength);
result->producerVersion[versionLength] = '\0';
result->versionCount = 1;
memcpy(result->versions[0].component, "Network", sizeof("Network"));
result->versions[0].version = recovery->serviceVersion;
result->requestSerial = recovery->requestSerial;
result->request = recovery->request;
result->ackSerial = recovery->ackSerial;
result->ackRequest = recovery->ackRequest;
result->state = state;
result->error = error;
return true;
}
LG_TransportStatus networkRecoveryGetInfo(NetworkRecovery * recovery,
LG_RecoveryInfo * info)
{
if (!recovery || !info)
return LG_TRANSPORT_ERROR;
uint64_t queryID;
uint16_t version;
if (!networkRecoveryBeginQuery(recovery, &queryID, &version))
{
LG_LOCK(recovery->lock);
const bool connected = recovery->connected;
const bool copied = networkRecoveryCopyInfoNL(recovery, info);
LG_UNLOCK(recovery->lock);
if (copied)
return LG_TRANSPORT_OK;
return connected ? LG_TRANSPORT_UNAVAILABLE : LG_TRANSPORT_DISCONNECTED;
}
LG_LOCK(recovery->lock);
bool copied = networkRecoveryCopyInfoNL(recovery, info);
LG_UNLOCK(recovery->lock);
if (copied)
return LG_TRANSPORT_OK;
lgWaitEvent(recovery->queryEvent, NETWORK_RECOVERY_QUERY_TIMEOUT_MS);
LG_LOCK(recovery->lock);
const bool connected = recovery->connected;
copied = networkRecoveryCopyInfoNL(recovery, info);
LG_UNLOCK(recovery->lock);
if (copied)
return LG_TRANSPORT_OK;
return connected ? LG_TRANSPORT_UNAVAILABLE : LG_TRANSPORT_DISCONNECTED;
}
LG_TransportStatus networkRecoveryRequest(NetworkRecovery * recovery,
LG_RecoveryRequest request, uint64_t * instance, uint32_t * serial)
{
if (!recovery || (request != LG_RECOVERY_REQ_NORMAL &&
request != LG_RECOVERY_REQ_RECOVERY))
return LG_TRANSPORT_ERROR;
LG_RecoveryInfo current;
const LG_TransportStatus infoStatus =
networkRecoveryGetInfo(recovery, &current);
if (infoStatus != LG_TRANSPORT_OK)
return infoStatus;
if (!(current.capabilities & LG_RECOVERY_CAP_DISPLAY))
return LG_TRANSPORT_UNAVAILABLE;
uint64_t id;
uint32_t requestSerial;
uint16_t version;
LGNetRecoveryRequest wire;
LG_LOCK(recovery->lock);
struct NetworkRecoveryRequestEntry * entry =
networkRecoveryReserveRequestNL(recovery);
if (!entry || !recovery->connected || !recovery->available ||
!recovery->infoValid ||
recovery->info.sessionID != current.instance)
{
LG_UNLOCK(recovery->lock);
return LG_TRANSPORT_UNAVAILABLE;
}
id = networkRecoveryNextID(recovery->nextID);
requestSerial = networkRecoveryNextSerial(recovery->nextSerial);
recovery->nextID = id;
recovery->nextSerial = requestSerial;
version = recovery->serviceVersion;
const uint32_t timeout = recovery->info.maxTransitionMs ?
recovery->info.maxTransitionMs : NETWORK_RECOVERY_DEFAULT_TIMEOUT_MS;
wire = (LGNetRecoveryRequest)
{
.requestID = id,
.sessionID = recovery->info.sessionID,
.expectedStatusSequence = recovery->info.statusSequence,
.timeoutMs = timeout > LG_NET_RECOVERY_MAX_TIMEOUT_MS ?
LG_NET_RECOVERY_MAX_TIMEOUT_MS : timeout,
.flags = request == LG_RECOVERY_REQ_RECOVERY ?
LG_NET_RECOVERY_REQUEST_ACTIVE : 0,
};
*entry = (struct NetworkRecoveryRequestEntry)
{
.id = id,
.session = wire.sessionID,
.serial = requestSerial,
.request = request,
.acknowledged = false,
};
LG_UNLOCK(recovery->lock);
uint8_t payload[LG_NET_RECOVERY_REQUEST_WIRE_SIZE];
if (!lgNetRecoveryRequestEncode(payload, sizeof(payload), &wire))
goto fail;
const LG_TransportStatus status = networkConnectionSend(
recovery->connection, LG_NET_SERVICE_RECOVERY,
LG_NET_RECOVERY_MESSAGE_REQUEST, version,
LG_NET_ENVELOPE_ACK_REQUIRED | LG_NET_ENVELOPE_FINAL,
0, id, payload, sizeof(payload));
if (status != LG_TRANSPORT_OK)
{
LG_LOCK(recovery->lock);
entry = networkRecoveryFindRequestNL(recovery, id);
if (entry)
*entry = (struct NetworkRecoveryRequestEntry) { 0 };
LG_UNLOCK(recovery->lock);
return status;
}
LG_LOCK(recovery->lock);
recovery->requestSerial = requestSerial;
recovery->request = request;
LG_UNLOCK(recovery->lock);
if (instance)
*instance = wire.sessionID;
if (serial)
*serial = requestSerial;
return LG_TRANSPORT_OK;
fail:
LG_LOCK(recovery->lock);
entry = networkRecoveryFindRequestNL(recovery, id);
if (entry)
*entry = (struct NetworkRecoveryRequestEntry) { 0 };
LG_UNLOCK(recovery->lock);
return LG_TRANSPORT_ERROR;
}

View File

@@ -0,0 +1,45 @@
/**
* 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
*/
#pragma once
#include "connection.h"
#include "interface/transport.h"
#include <LGProtocol/NetworkProtocol.h>
#include <stdbool.h>
typedef struct NetworkRecovery NetworkRecovery;
bool networkRecoveryCreate(NetworkRecovery ** recovery,
NetworkConnection * connection);
void networkRecoveryDestroy(NetworkRecovery ** recovery);
void networkRecoveryConnectionState(NetworkRecovery * recovery,
bool connected);
bool networkRecoveryPacket(NetworkRecovery * recovery,
const LGNetPacketView * packet, bool datagram);
LG_TransportStatus networkRecoveryGetInfo(NetworkRecovery * recovery,
LG_RecoveryInfo * info);
LG_TransportStatus networkRecoveryRequest(NetworkRecovery * recovery,
LG_RecoveryRequest request, uint64_t * instance, uint32_t * serial);

File diff suppressed because it is too large Load Diff

View File

@@ -20,8 +20,46 @@
#include "transport/network/CNetworkSession.h" #include "transport/network/CNetworkSession.h"
#include <new>
#include <utility> #include <utility>
namespace
{
uint64_t NextRevision(uint64_t revision)
{
++revision;
if (!revision)
++revision;
return revision;
}
LGNetControlStatusCode ControlStatusCode(InteractionResult result)
{
switch (result)
{
case InteractionResult::ACCEPTED:
return LG_NET_CONTROL_STATUS_APPLIED;
case InteractionResult::BUSY:
return LG_NET_CONTROL_STATUS_BUSY;
case InteractionResult::UNAVAILABLE:
return LG_NET_CONTROL_STATUS_UNSUPPORTED;
case InteractionResult::STALE:
return LG_NET_CONTROL_STATUS_STALE;
case InteractionResult::REJECTED:
return LG_NET_CONTROL_STATUS_INVALID;
case InteractionResult::FAILED:
return LG_NET_CONTROL_STATUS_ERROR;
}
return LG_NET_CONTROL_STATUS_ERROR;
}
}
bool CNetworkEventQueue::Push(const NetworkEvent& event) bool CNetworkEventQueue::Push(const NetworkEvent& event)
{ {
CSRWExclusiveLock lock(m_lock); CSRWExclusiveLock lock(m_lock);
@@ -147,47 +185,87 @@ ControlResult CNetworkControlSink::SendCursor(
{ {
const bool shape = info.CursorShapeInfo.CursorType != const bool shape = info.CursorShapeInfo.CursorType !=
IDDCX_CURSOR_SHAPE_TYPE_UNINITIALIZED; IDDCX_CURSOR_SHAPE_TYPE_UNINITIALIZED;
if (shape && info.CursorShapeInfo.CursorType !=
IDDCX_CURSOR_SHAPE_TYPE_ALPHA &&
info.CursorShapeInfo.CursorType !=
IDDCX_CURSOR_SHAPE_TYPE_MASKED_COLOR)
return ControlResult::FAILED;
if (shape && info.CursorShapeInfo.Height && if (shape && info.CursorShapeInfo.Height &&
info.CursorShapeInfo.Pitch > info.CursorShapeInfo.Pitch >
SIZE_MAX / info.CursorShapeInfo.Height) SIZE_MAX / info.CursorShapeInfo.Height)
return ControlResult::FAILED; return ControlResult::FAILED;
const size_t shapeSize = shape ? static_cast<size_t>( const size_t shapeSize = shape ? static_cast<size_t>(
info.CursorShapeInfo.Height) * info.CursorShapeInfo.Pitch : 0; info.CursorShapeInfo.Height) * info.CursorShapeInfo.Pitch : 0;
if (size != shapeSize || (size && !data)) if (size != shapeSize || (size && !data) ||
shapeSize > LG_NET_CURSOR_MAX_SHAPE_LENGTH ||
(shape && (!info.CursorShapeInfo.Width ||
info.CursorShapeInfo.Width > LG_NET_CURSOR_MAX_WIDTH ||
!info.CursorShapeInfo.Height ||
info.CursorShapeInfo.Height > LG_NET_CURSOR_MAX_HEIGHT ||
info.CursorShapeInfo.XHot >= info.CursorShapeInfo.Width ||
info.CursorShapeInfo.YHot >= info.CursorShapeInfo.Height ||
info.CursorShapeInfo.Pitch < info.CursorShapeInfo.Width * 4U)))
return ControlResult::FAILED; return ControlResult::FAILED;
std::vector<BYTE> cursorData; std::vector<BYTE> cursorData;
if (shapeSize) try
cursorData.assign(data, data + size); {
if (shapeSize)
cursorData.assign(data, data + size);
}
catch (...)
{
return ControlResult::FAILED;
}
CSRWExclusiveLock lock(m_lock); CSRWExclusiveLock lock(m_lock);
if (!m_events) if (!m_events)
return ControlResult::RETRY; return ControlResult::RETRY;
const UINT whiteLevel = sdrWhiteLevel ?
sdrWhiteLevel : LG_SDR_WHITE_LEVEL_DEFAULT;
if (m_state.sdrWhiteLevel != whiteLevel)
{
m_state.sdrWhiteLevel = whiteLevel;
m_state.colorRevision = NextRevision(m_state.colorRevision);
}
m_state.cursor.IsCursorVisible = info.IsCursorVisible; m_state.cursor.IsCursorVisible = info.IsCursorVisible;
m_state.cursor.X = info.X; m_state.cursor.X = info.X;
m_state.cursor.Y = info.Y; m_state.cursor.Y = info.Y;
m_state.sdrWhiteLevel = sdrWhiteLevel;
m_state.cursorValid = true; m_state.cursorValid = true;
m_state.positionRevision = NextRevision(m_state.positionRevision);
if (shape) if (shape)
{ {
m_state.cursor.IsCursorShapeUpdated = info.IsCursorShapeUpdated; m_state.cursor.IsCursorShapeUpdated = info.IsCursorShapeUpdated;
m_state.cursor.CursorShapeInfo = info.CursorShapeInfo; m_state.cursor.CursorShapeInfo = info.CursorShapeInfo;
m_state.cursorData = std::move(cursorData); m_state.cursorData = std::move(cursorData);
m_state.shapeValid = true; m_state.shapeValid = true;
m_state.shapeRevision = NextRevision(m_state.shapeRevision);
} }
++m_state.revision; m_state.stateRevision = NextRevision(m_state.stateRevision);
return ControlResult::APPLIED; return ControlResult::APPLIED;
} }
ControlResult CNetworkControlSink::SetColorTransform( ControlResult CNetworkControlSink::SetColorTransform(
std::shared_ptr<const D12ColorTransform> transform) std::shared_ptr<const D12ColorTransform> transform)
{ {
std::shared_ptr<const D12ColorTransform> copy;
try
{
if (transform)
copy = std::make_shared<D12ColorTransform>(*transform);
}
catch (...)
{
return ControlResult::FAILED;
}
CSRWExclusiveLock lock(m_lock); CSRWExclusiveLock lock(m_lock);
if (!m_events) if (!m_events)
return ControlResult::RETRY; return ControlResult::RETRY;
m_state.transform = std::move(transform); m_state.transform = std::move(copy);
m_state.transformValid = true; m_state.transformValid = true;
++m_state.revision; m_state.colorRevision = NextRevision(m_state.colorRevision);
m_state.stateRevision = NextRevision(m_state.stateRevision);
return ControlResult::APPLIED; return ControlResult::APPLIED;
} }
@@ -198,12 +276,21 @@ void CNetworkControlSink::RequestReplay()
m_events->OnControlReplay(m_token); m_events->OnControlReplay(m_token);
} }
bool CNetworkControlSink::Snapshot(NetworkControlSnapshot& state) const bool CNetworkControlSink::Snapshot(
NetworkControlSnapshot& state, uint64_t knownRevision) const
{ {
CSRWSharedLock lock(m_lock); CSRWSharedLock lock(m_lock);
if (!m_events) if (!m_events || !m_state.stateRevision ||
m_state.stateRevision == knownRevision)
return false; return false;
state = m_state; try
{
state = m_state;
}
catch (...)
{
return false;
}
return true; return true;
} }
@@ -243,31 +330,50 @@ void CNetworkSession::Close()
m_input.Release(m_source, true); m_input.Release(m_source, true);
} }
bool CNetworkSession::SetCursorPosition(int32_t x, int32_t y) bool CNetworkSession::SetCursorPosition(
uint64_t controlID, int32_t x, int32_t y)
{ {
CSRWSharedLock lock(m_lifecycleLock); CSRWSharedLock lock(m_lifecycleLock);
if (!m_open) if (!m_open || !controlID)
return false; return false;
NetworkEvent event; NetworkEvent event;
event.type = NetworkEventType::SET_CURSOR_POSITION; event.type = NetworkEventType::SET_CURSOR_POSITION;
event.source = EventSource(); event.source = EventSource();
event.x = x; event.controlID = controlID;
event.y = y; event.x = x;
event.y = y;
return m_events.Push(event); return m_events.Push(event);
} }
bool CNetworkSession::SetResolution(uint32_t width, uint32_t height) bool CNetworkSession::SetResolution(
uint64_t controlID, uint32_t width, uint32_t height)
{ {
CSRWSharedLock lock(m_lifecycleLock); CSRWSharedLock lock(m_lifecycleLock);
if (!m_open || !width || !height) if (!m_open || !controlID || !width || !height)
return false; return false;
NetworkEvent event; NetworkEvent event;
event.type = NetworkEventType::SET_RESOLUTION; event.type = NetworkEventType::SET_RESOLUTION;
event.source = EventSource(); event.source = EventSource();
event.width = width; event.controlID = controlID;
event.height = height; event.width = width;
event.height = height;
return m_events.Push(event);
}
bool CNetworkSession::SetFrameSchedule(
uint64_t controlID, const FrameScheduleUpdate& schedule)
{
CSRWSharedLock lock(m_lifecycleLock);
if (!m_open || !controlID)
return false;
NetworkEvent event;
event.type = NetworkEventType::SET_FRAME_SCHEDULE;
event.source = EventSource();
event.schedule = schedule;
event.controlID = controlID;
return m_events.Push(event); return m_events.Push(event);
} }
@@ -298,12 +404,33 @@ void CNetworkSession::ProcessEvent(
switch (event.type) switch (event.type)
{ {
case NetworkEventType::SET_CURSOR_POSITION: case NetworkEventType::SET_CURSOR_POSITION:
events.OnSetCursorPos(event.source, event.x, event.y); {
NetworkControlStatus status;
status.controlID = event.controlID;
status.status = ControlStatusCode(
events.OnSetCursorPos(event.source, event.x, event.y));
SetControlStatus(status);
break; break;
}
case NetworkEventType::SET_RESOLUTION: case NetworkEventType::SET_RESOLUTION:
events.OnSetResolution(event.source, event.width, event.height); {
NetworkControlStatus status;
status.controlID = event.controlID;
status.status = ControlStatusCode(events.OnSetResolution(
event.source, event.width, event.height));
SetControlStatus(status);
break; break;
}
case NetworkEventType::SET_FRAME_SCHEDULE:
{
NetworkControlStatus status;
status.controlID = event.controlID;
status.status = LG_NET_CONTROL_STATUS_UNSUPPORTED;
SetControlStatus(status);
break;
}
case NetworkEventType::RECOVERY_REQUEST: case NetworkEventType::RECOVERY_REQUEST:
{ {
@@ -356,6 +483,36 @@ bool CNetworkSession::TakeRecoveryStatus(NetworkRecoveryStatus& status)
return true; return true;
} }
void CNetworkSession::SetControlStatus(
const NetworkControlStatus& status)
{
CSRWExclusiveLock lock(m_controlLock);
if (m_controlCount == CONTROL_CAPACITY)
{
m_control[m_controlHead] = {};
m_controlHead = (m_controlHead + 1) % CONTROL_CAPACITY;
--m_controlCount;
}
const unsigned tail =
(m_controlHead + m_controlCount) % CONTROL_CAPACITY;
m_control[tail] = status;
++m_controlCount;
}
bool CNetworkSession::TakeControlStatus(NetworkControlStatus& status)
{
CSRWExclusiveLock lock(m_controlLock);
if (!m_controlCount)
return false;
status = m_control[m_controlHead];
m_control[m_controlHead] = {};
m_controlHead = (m_controlHead + 1) % CONTROL_CAPACITY;
--m_controlCount;
return true;
}
InputTargetState CNetworkSession::InputState() const InputTargetState CNetworkSession::InputState() const
{ {
CSRWSharedLock lock(m_lifecycleLock); CSRWSharedLock lock(m_lifecycleLock);

View File

@@ -21,10 +21,13 @@
#pragma once #pragma once
#include "CSRWLock.h" #include "CSRWLock.h"
#include "capture/CFrameScheduler.h"
#include "transport/IControlSink.h" #include "transport/IControlSink.h"
#include "transport/IInputSource.h" #include "transport/IInputSource.h"
#include "transport/ITransport.h" #include "transport/ITransport.h"
#include <LGProtocol/NetworkServices.h>
#include <stdint.h> #include <stdint.h>
#include <vector> #include <vector>
@@ -32,20 +35,23 @@ enum class NetworkEventType : uint8_t
{ {
SET_CURSOR_POSITION, SET_CURSOR_POSITION,
SET_RESOLUTION, SET_RESOLUTION,
SET_FRAME_SCHEDULE,
RECOVERY_REQUEST, RECOVERY_REQUEST,
}; };
struct NetworkEvent struct NetworkEvent
{ {
NetworkEventType type = NetworkEventType::SET_CURSOR_POSITION; NetworkEventType type = NetworkEventType::SET_CURSOR_POSITION;
SourceKey source; SourceKey source;
int32_t x = 0; FrameScheduleUpdate schedule = {};
int32_t y = 0; uint64_t controlID = 0;
uint32_t width = 0; int32_t x = 0;
uint32_t height = 0; int32_t y = 0;
uint64_t session = 0; uint32_t width = 0;
uint32_t serial = 0; uint32_t height = 0;
bool active = false; uint64_t session = 0;
uint32_t serial = 0;
bool active = false;
}; };
struct NetworkRecoveryStatus struct NetworkRecoveryStatus
@@ -57,13 +63,20 @@ struct NetworkRecoveryStatus
uint32_t error = 0; uint32_t error = 0;
}; };
struct NetworkControlStatus
{
uint64_t controlID = 0;
LGNetControlStatusCode status = LG_NET_CONTROL_STATUS_ERROR;
uint32_t detail = 0;
};
class CNetworkEventQueue class CNetworkEventQueue
{ {
private: private:
static constexpr unsigned CAPACITY = 128; static constexpr unsigned CAPACITY = 128;
CSRWLock m_lock; CSRWLock m_lock;
NetworkEvent m_events[CAPACITY] = {}; NetworkEvent m_events[CAPACITY] = {};
unsigned m_head = 0; unsigned m_head = 0;
unsigned m_count = 0; unsigned m_count = 0;
@@ -101,11 +114,14 @@ struct NetworkControlSnapshot
IDARG_OUT_QUERY_HWCURSOR cursor = {}; IDARG_OUT_QUERY_HWCURSOR cursor = {};
std::vector<BYTE> cursorData; std::vector<BYTE> cursorData;
std::shared_ptr<const D12ColorTransform> transform; std::shared_ptr<const D12ColorTransform> transform;
UINT sdrWhiteLevel = 0; UINT sdrWhiteLevel = 0;
uint64_t revision = 0; uint64_t stateRevision = 0;
bool cursorValid = false; uint64_t positionRevision = 0;
bool shapeValid = false; uint64_t shapeRevision = 0;
bool transformValid = false; uint64_t colorRevision = 0;
bool cursorValid = false;
bool shapeValid = false;
bool transformValid = false;
}; };
class CNetworkControlSink final : public IControlSink class CNetworkControlSink final : public IControlSink
@@ -125,7 +141,8 @@ public:
std::shared_ptr<const D12ColorTransform> transform) override; std::shared_ptr<const D12ColorTransform> transform) override;
void RequestReplay(); void RequestReplay();
bool Snapshot(NetworkControlSnapshot& state) const; bool Snapshot(
NetworkControlSnapshot& state, uint64_t knownRevision) const;
}; };
// A session is transport-independent state owned by the eventual QUIC // A session is transport-independent state owned by the eventual QUIC
@@ -135,6 +152,7 @@ class CNetworkSession final
{ {
private: private:
static constexpr unsigned RECOVERY_CAPACITY = 16; static constexpr unsigned RECOVERY_CAPACITY = 16;
static constexpr unsigned CONTROL_CAPACITY = 128;
CNetworkEventQueue& m_events; CNetworkEventQueue& m_events;
CNetworkInputSource& m_input; CNetworkInputSource& m_input;
@@ -143,10 +161,15 @@ private:
bool m_open = true; bool m_open = true;
CSRWLock m_recoveryLock; CSRWLock m_recoveryLock;
NetworkRecoveryStatus m_recovery[RECOVERY_CAPACITY] = {}; NetworkRecoveryStatus m_recovery[RECOVERY_CAPACITY] = {};
unsigned m_recoveryHead = 0; unsigned m_recoveryHead = 0;
unsigned m_recoveryCount = 0; unsigned m_recoveryCount = 0;
CSRWLock m_controlLock;
NetworkControlStatus m_control[CONTROL_CAPACITY] = {};
unsigned m_controlHead = 0;
unsigned m_controlCount = 0;
SourceKey EventSource() const; SourceKey EventSource() const;
public: public:
@@ -161,12 +184,17 @@ public:
bool IsOpen() const; bool IsOpen() const;
void Close(); void Close();
bool SetCursorPosition(int32_t x, int32_t y); bool SetCursorPosition(uint64_t controlID, int32_t x, int32_t y);
bool SetResolution(uint32_t width, uint32_t height); bool SetResolution(
uint64_t controlID, uint32_t width, uint32_t height);
bool SetFrameSchedule(
uint64_t controlID, const FrameScheduleUpdate& schedule);
bool RequestRecovery(uint64_t session, uint32_t serial, bool active); bool RequestRecovery(uint64_t session, uint32_t serial, bool active);
void ProcessEvent(const NetworkEvent& event, ITransportEvents& events); void ProcessEvent(const NetworkEvent& event, ITransportEvents& events);
void SetRecoveryStatus(const NetworkRecoveryStatus& status); void SetRecoveryStatus(const NetworkRecoveryStatus& status);
bool TakeRecoveryStatus(NetworkRecoveryStatus& status); bool TakeRecoveryStatus(NetworkRecoveryStatus& status);
void SetControlStatus(const NetworkControlStatus& status);
bool TakeControlStatus(NetworkControlStatus& status);
InputTargetState InputState() const; InputTargetState InputState() const;
InputResult ClaimInput(); InputResult ClaimInput();

View File

@@ -241,6 +241,7 @@ bool CNetworkTransport::ServiceEnabled(LGNetService service) const
{ {
case LG_NET_SERVICE_RECOVERY: case LG_NET_SERVICE_RECOVERY:
case LG_NET_SERVICE_CURSOR: case LG_NET_SERVICE_CURSOR:
case LG_NET_SERVICE_CONTROL:
return (m_config.services & TRANSPORT_SERVICE_CONTROL) != 0; return (m_config.services & TRANSPORT_SERVICE_CONTROL) != 0;
case LG_NET_SERVICE_INPUT: case LG_NET_SERVICE_INPUT:
@@ -265,9 +266,9 @@ bool CNetworkTransport::ServiceEnabled(LGNetService service) const
} }
bool CNetworkTransport::ControlSnapshot( bool CNetworkTransport::ControlSnapshot(
NetworkControlSnapshot& state) const NetworkControlSnapshot& state, uint64_t knownRevision) const
{ {
return m_control.Snapshot(state); return m_control.Snapshot(state, knownRevision);
} }
std::shared_ptr<CNetworkSession> CNetworkTransport::CreateSession( std::shared_ptr<CNetworkSession> CNetworkTransport::CreateSession(

View File

@@ -94,7 +94,8 @@ public:
const NetworkSettings& Settings() const { return m_settings; } const NetworkSettings& Settings() const { return m_settings; }
bool ServiceEnabled(LGNetService service) const; bool ServiceEnabled(LGNetService service) const;
bool ControlSnapshot(NetworkControlSnapshot& state) const; bool ControlSnapshot(
NetworkControlSnapshot& state, uint64_t knownRevision) const;
// The QUIC provider calls these only after authenticating a peer. Keeping // The QUIC provider calls these only after authenticating a peer. Keeping
// the registry here makes Stop a barrier for session-originated callbacks. // the registry here makes Stop a barrier for session-originated callbacks.