Files
LookingGlass/client/include/interface/clipboard.h
Geoffrey McRae 6631fde35f [client] clipboard: reduce remote file transfer latency
Mark the last non-empty FILE_DATA payload with BEGIN and END when the
producer knows it is terminal. Complete producer bookkeeping in the
following fileDataEnd call without consuming another LGMP grant. Keep
the standalone terminal record for empty and legacy unknown streams.

Drain up to 64 queued clipboard records per worker pass instead of
sleeping after every message. Poll at 1 ms while transfers, blocked
writes, held input, or pending output are active, and retain the 10 ms
interval while idle.

This removes a serialized grant round trip from every non-empty file
response and reduces scheduling delay while a large transfer is active.
Keep the 1 MiB payload size because the bottleneck was stop-and-wait
latency, not the chunk capacity.

Exercise a full 1 MiB response in a single BEGIN|END grant and retain
coverage for empty unknown-size responses.
2026-08-15 01:13:13 +10:00

264 lines
10 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
*/
#ifndef _H_LG_CLIENT_CLIPBOARD_INTERFACE_
#define _H_LG_CLIENT_CLIPBOARD_INTERFACE_
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
typedef enum LG_ClipboardData
{
LG_CLIPBOARD_DATA_TEXT = 0,
LG_CLIPBOARD_DATA_PNG,
LG_CLIPBOARD_DATA_BMP,
LG_CLIPBOARD_DATA_TIFF,
LG_CLIPBOARD_DATA_JPEG,
LG_CLIPBOARD_DATA_FILES,
LG_CLIPBOARD_DATA_NONE
}
LG_ClipboardData;
typedef void (*LG_ClipboardReplyFn)(void * opaque,
LG_ClipboardData type, const uint8_t * data, uint32_t size);
typedef uint64_t LG_ClipboardRequest;
#define LG_CLIPBOARD_REQUEST_INVALID UINT64_C(0)
#define LG_CLIPBOARD_SIZE_UNKNOWN UINT64_MAX
typedef enum LG_ClipboardResult
{
/* The operation is terminal and consumed no data. */
LG_CLIPBOARD_RESULT_FAILED = 0,
/* The operation, including the complete chunk, was accepted. */
LG_CLIPBOARD_RESULT_ACCEPTED = 1,
/* Nothing was consumed. Retry only after the matching ready callback. */
LG_CLIPBOARD_RESULT_BLOCKED = 2,
}
LG_ClipboardResult;
typedef enum LG_ClipboardCancelReason
{
LG_CLIPBOARD_CANCEL_ABORTED = 0,
LG_CLIPBOARD_CANCEL_REPLACED = 1,
LG_CLIPBOARD_CANCEL_UNAVAILABLE = 2,
LG_CLIPBOARD_CANCEL_INVALID = 3,
}
LG_ClipboardCancelReason;
typedef enum LG_ClipboardFileOperation
{
LG_CLIPBOARD_FILE_LIST = 1,
LG_CLIPBOARD_FILE_READ = 2,
}
LG_ClipboardFileOperation;
typedef enum LG_ClipboardFileError
{
LG_CLIPBOARD_FILE_ERROR_NONE = 0,
LG_CLIPBOARD_FILE_ERROR_NOT_FOUND,
LG_CLIPBOARD_FILE_ERROR_ACCESS,
LG_CLIPBOARD_FILE_ERROR_NOT_DIRECTORY,
LG_CLIPBOARD_FILE_ERROR_IS_DIRECTORY,
LG_CLIPBOARD_FILE_ERROR_IO,
LG_CLIPBOARD_FILE_ERROR_INVALID,
LG_CLIPBOARD_FILE_ERROR_NO_MEMORY,
LG_CLIPBOARD_FILE_ERROR_NO_SPACE,
LG_CLIPBOARD_FILE_ERROR_DISCONNECTED,
LG_CLIPBOARD_FILE_ERROR_CANCELLED,
LG_CLIPBOARD_FILE_ERROR_NOT_SUPPORTED,
LG_CLIPBOARD_FILE_ERROR_STALE,
}
LG_ClipboardFileError;
typedef struct LG_ClipboardFileRequest
{
uint64_t dataset;
uint64_t request;
uint64_t node;
uint64_t offset;
uint32_t length;
LG_ClipboardFileOperation operation;
}
LG_ClipboardFileRequest;
/* A consumer of a provider-to-client stream. Callbacks are serialized and
* buffers are borrowed only for the duration of chunk(). A callback which
* returns BLOCKED consumes nothing; the consumer must subsequently call
* its request-ready entry point before the same operation can be retried. */
typedef struct LG_ClipboardStreamOps
{
LG_ClipboardResult (*begin)(void * opaque, LG_ClipboardData type,
uint64_t sizeHint);
LG_ClipboardResult (*chunk)(void * opaque, uint64_t offset,
const void * data, size_t size);
LG_ClipboardResult (*end)(void * opaque, uint64_t finalSize);
void (*cancel)(void * opaque, LG_ClipboardCancelReason reason);
}
LG_ClipboardStreamOps;
typedef struct LG_ClipboardStatus
{
bool available;
uint32_t generation;
}
LG_ClipboardStatus;
typedef void (*LG_ClipboardStatusFn)(void * opaque,
const LG_ClipboardStatus * status);
typedef struct LG_ClipboardEventOps
{
/* The type list and data are borrowed for the duration of the callback.
* Event callbacks are always invoked after releasing backend locks. */
void (*notice)(void * opaque, const LG_ClipboardData types[], size_t count);
void (*data)(void * opaque, LG_ClipboardRequest request,
LG_ClipboardData type, const void * data, size_t size);
LG_ClipboardResult (*dataBegin)(void * opaque,
LG_ClipboardRequest request, LG_ClipboardData type,
uint64_t sizeHint);
LG_ClipboardResult (*dataChunk)(void * opaque,
LG_ClipboardRequest request, uint64_t offset,
const void * data, size_t size);
LG_ClipboardResult (*dataEnd)(void * opaque,
LG_ClipboardRequest request, uint64_t finalSize);
void (*dataCancel)(void * opaque, LG_ClipboardRequest request,
LG_ClipboardCancelReason reason);
/* Signals that the provider can accept a retry of the operation which most
* recently returned BLOCKED for its request. */
void (*dataReady)(void * opaque, LG_ClipboardRequest request);
/* Cancels a request previously delivered through request(). */
void (*requestCancel)(void * opaque, LG_ClipboardRequest request,
LG_ClipboardCancelReason reason);
void (*release)(void * opaque);
bool (*request)(void * opaque, LG_ClipboardRequest request,
LG_ClipboardData type);
/* File datasets use independent, multiplexed request streams. Dataset and
* node identifiers are opaque outside the publisher. */
void (*fileOffer)(void * opaque, uint64_t dataset);
void (*fileAcquire)(void * opaque, uint64_t dataset,
uint64_t acquisition);
void (*fileAcquired)(void * opaque, uint64_t dataset,
uint64_t acquisition, LG_ClipboardFileError error);
void (*fileRelease)(void * opaque, uint64_t dataset,
uint64_t acquisition);
void (*fileRequest)(void * opaque,
const LG_ClipboardFileRequest * request);
LG_ClipboardResult (*fileDataBegin)(void * opaque,
const LG_ClipboardFileRequest * request, uint64_t sizeHint);
LG_ClipboardResult (*fileDataChunk)(void * opaque,
const LG_ClipboardFileRequest * request, uint64_t responseOffset,
const void * data, size_t size);
LG_ClipboardResult (*fileDataEnd)(void * opaque,
const LG_ClipboardFileRequest * request, uint64_t finalSize);
void (*fileDataReady)(void * opaque, uint64_t request);
void (*fileCancel)(void * opaque, uint64_t dataset,
uint64_t request, LG_ClipboardFileError reason);
}
LG_ClipboardEventOps;
typedef struct LG_ClipboardOps
{
const char * name;
/* Operations must fail safely if the remote endpoint disappears. */
/* Registration must synchronously report the current status after releasing
* any backend locks. Status callbacks must be serialized and generations
* must advance whenever availability changes. Passing NULL unregisters the
* listener and synchronously quiesces its callbacks. Status callbacks must
* not be delivered from another operation or event callback. */
void (*setStatusListener)(void * opaque, LG_ClipboardStatusFn callback,
void * callbackOpaque);
/* Attach begins serialized event delivery and may synchronously replay the
* current remote notice. Detach synchronously quiesces event callbacks.
* Other operations must not deliver event callbacks synchronously. */
bool (*attach)(void * opaque, const LG_ClipboardEventOps * events,
void * eventOpaque);
void (*detach)(void * opaque);
bool (*release)(void * opaque);
/* All outbound arrays and data are borrowed only until the call returns. */
bool (*notifyTypes)(void * opaque, const LG_ClipboardData types[],
size_t count);
/* Publishes an immutable local file dataset. The nonzero dataset ID is
* process-unique and is used as the wire clipboard generation. */
bool (*offerFiles)(void * opaque, uint64_t dataset);
/* Data is a complete response to a remote request. NONE with no payload
* reports that the request could not be completed. This operation is the
* legacy whole-buffer alternative to the stream operation group below. */
bool (*data)(void * opaque, LG_ClipboardRequest request,
LG_ClipboardData type, const void * data, size_t size);
/* Streaming providers implement this complete group instead of data().
* BEGIN may use LG_CLIPBOARD_SIZE_UNKNOWN. CHUNK is all-or-nothing and its
* offset must be the next byte in the stream. END's size is authoritative.
* A BLOCKED operation consumes nothing and is retried only after dataReady
* is delivered through the attached event operations. Ready callbacks must
* be serialized with other events, delivered without backend locks held,
* and never delivered synchronously from one of these operations. */
LG_ClipboardResult (*dataBegin)(void * opaque,
LG_ClipboardRequest request, LG_ClipboardData type,
uint64_t sizeHint);
LG_ClipboardResult (*dataChunk)(void * opaque,
LG_ClipboardRequest request, uint64_t offset,
const void * data, size_t size);
LG_ClipboardResult (*dataEnd)(void * opaque,
LG_ClipboardRequest request, uint64_t finalSize);
bool (*dataCancel)(void * opaque, LG_ClipboardRequest request,
LG_ClipboardCancelReason reason);
/* Signals that the client can accept a retry of a provider-to-client stream
* operation which returned BLOCKED. */
bool (*dataReady)(void * opaque, LG_ClipboardRequest request);
/* A successful request produces exactly one matching data event unless the
* provider is detached, becomes unavailable, or publishes a newer notice
* or release first. */
bool (*request)(void * opaque, LG_ClipboardRequest request,
LG_ClipboardData type);
bool (*fileAcquire)(void * opaque, uint64_t dataset,
uint64_t acquisition);
bool (*fileAcquired)(void * opaque, uint64_t dataset,
uint64_t acquisition, LG_ClipboardFileError error);
bool (*fileRelease)(void * opaque, uint64_t dataset,
uint64_t acquisition);
bool (*fileRequest)(void * opaque,
const LG_ClipboardFileRequest * request);
LG_ClipboardResult (*fileDataBegin)(void * opaque,
const LG_ClipboardFileRequest * request, uint64_t sizeHint);
/* end marks a final non-empty chunk. fileDataEnd must still be called to
* complete the producer-side stream; it emits a terminal record only when
* the final chunk was not marked, including for an empty response. */
LG_ClipboardResult (*fileDataChunk)(void * opaque,
const LG_ClipboardFileRequest * request, uint64_t responseOffset,
const void * data, size_t size, bool end);
LG_ClipboardResult (*fileDataEnd)(void * opaque,
const LG_ClipboardFileRequest * request, uint64_t finalSize);
bool (*fileCancel)(void * opaque, uint64_t dataset,
uint64_t request, LG_ClipboardFileError reason);
}
LG_ClipboardOps;
#endif