mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 07:01:30 +00:00
[client] clipboard: introduce streamed transfers
This commit is contained in:
@@ -43,6 +43,42 @@ typedef void (*LG_ClipboardReplyFn)(void * opaque,
|
|||||||
typedef uint64_t LG_ClipboardRequest;
|
typedef uint64_t LG_ClipboardRequest;
|
||||||
|
|
||||||
#define LG_CLIPBOARD_REQUEST_INVALID UINT64_C(0)
|
#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;
|
||||||
|
|
||||||
|
/* 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
|
typedef struct LG_ClipboardStatus
|
||||||
{
|
{
|
||||||
@@ -61,6 +97,22 @@ typedef struct LG_ClipboardEventOps
|
|||||||
void (*notice)(void * opaque, const LG_ClipboardData types[], size_t count);
|
void (*notice)(void * opaque, const LG_ClipboardData types[], size_t count);
|
||||||
void (*data)(void * opaque, LG_ClipboardRequest request,
|
void (*data)(void * opaque, LG_ClipboardRequest request,
|
||||||
LG_ClipboardData type, const void * data, size_t size);
|
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);
|
void (*release)(void * opaque);
|
||||||
bool (*request)(void * opaque, LG_ClipboardRequest request,
|
bool (*request)(void * opaque, LG_ClipboardRequest request,
|
||||||
LG_ClipboardData type);
|
LG_ClipboardData type);
|
||||||
@@ -93,10 +145,30 @@ typedef struct LG_ClipboardOps
|
|||||||
bool (*notifyTypes)(void * opaque, const LG_ClipboardData types[],
|
bool (*notifyTypes)(void * opaque, const LG_ClipboardData types[],
|
||||||
size_t count);
|
size_t count);
|
||||||
/* Data is a complete response to a remote request. NONE with no payload
|
/* Data is a complete response to a remote request. NONE with no payload
|
||||||
* reports that the request could not be completed. The provider must finish
|
* reports that the request could not be completed. This operation is the
|
||||||
* any transport framing before returning. */
|
* legacy whole-buffer alternative to the stream operation group below. */
|
||||||
bool (*data)(void * opaque, LG_ClipboardRequest request,
|
bool (*data)(void * opaque, LG_ClipboardRequest request,
|
||||||
LG_ClipboardData type, const void * data, size_t size);
|
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
|
/* A successful request produces exactly one matching data event unless the
|
||||||
* provider is detached, becomes unavailable, or publishes a newer notice
|
* provider is detached, becomes unavailable, or publishes a newer notice
|
||||||
* or release first. */
|
* or release first. */
|
||||||
|
|||||||
@@ -249,6 +249,9 @@ struct LG_DisplayServerOps
|
|||||||
void (*cbNotice)(LG_ClipboardData type);
|
void (*cbNotice)(LG_ClipboardData type);
|
||||||
void (*cbRelease)(void);
|
void (*cbRelease)(void);
|
||||||
void (*cbRequest)(LG_ClipboardRequest request, LG_ClipboardData type);
|
void (*cbRequest)(LG_ClipboardRequest request, LG_ClipboardData type);
|
||||||
|
void (*cbRequestReady)(LG_ClipboardRequest request);
|
||||||
|
void (*cbRequestCancel)(LG_ClipboardRequest request,
|
||||||
|
LG_ClipboardCancelReason reason);
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef ENABLE_EGL
|
#ifdef ENABLE_EGL
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -36,9 +36,19 @@ void lgClipboard_dropTransport(void);
|
|||||||
void lgClipboard_release(void);
|
void lgClipboard_release(void);
|
||||||
void lgClipboard_notifyTypes(
|
void lgClipboard_notifyTypes(
|
||||||
const LG_ClipboardData types[], size_t count);
|
const LG_ClipboardData types[], size_t count);
|
||||||
|
LG_ClipboardResult lgClipboard_dataBegin(LG_ClipboardRequest request,
|
||||||
|
LG_ClipboardData type, uint64_t sizeHint);
|
||||||
|
LG_ClipboardResult lgClipboard_dataChunk(LG_ClipboardRequest request,
|
||||||
|
uint64_t offset, const void * data, size_t size);
|
||||||
|
LG_ClipboardResult lgClipboard_dataEnd(LG_ClipboardRequest request,
|
||||||
|
uint64_t finalSize);
|
||||||
void lgClipboard_data(LG_ClipboardRequest request,
|
void lgClipboard_data(LG_ClipboardRequest request,
|
||||||
LG_ClipboardData type, const void * data, size_t size);
|
LG_ClipboardData type, const void * data, size_t size);
|
||||||
void lgClipboard_abort(LG_ClipboardRequest request);
|
void lgClipboard_abort(LG_ClipboardRequest request);
|
||||||
|
bool lgClipboard_requestStream(LG_ClipboardData type,
|
||||||
|
const LG_ClipboardStreamOps * stream, void * opaque,
|
||||||
|
LG_ClipboardRequest * request);
|
||||||
|
bool lgClipboard_requestReady(LG_ClipboardRequest request);
|
||||||
bool lgClipboard_request(LG_ClipboardData type,
|
bool lgClipboard_request(LG_ClipboardData type,
|
||||||
LG_ClipboardReplyFn replyFn, void * opaque);
|
LG_ClipboardReplyFn replyFn, void * opaque);
|
||||||
|
|
||||||
|
|||||||
@@ -428,6 +428,10 @@ set(CLIPBOARD_CASES
|
|||||||
generation
|
generation
|
||||||
local
|
local
|
||||||
reentrant
|
reentrant
|
||||||
|
stream-local
|
||||||
|
stream-blocked
|
||||||
|
stream-remote
|
||||||
|
stream-legacy
|
||||||
)
|
)
|
||||||
foreach(name IN LISTS CLIPBOARD_CASES)
|
foreach(name IN LISTS CLIPBOARD_CASES)
|
||||||
add_test(NAME clipboard-${name}
|
add_test(NAME clipboard-${name}
|
||||||
@@ -438,6 +442,34 @@ foreach(name IN LISTS CLIPBOARD_CASES)
|
|||||||
)
|
)
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
|
add_executable(spice-clipboard-tests
|
||||||
|
spice_clipboard_test.c
|
||||||
|
../src/clipboard.c
|
||||||
|
../transports/SPICE/clipboard.c
|
||||||
|
)
|
||||||
|
target_compile_definitions(spice-clipboard-tests PRIVATE
|
||||||
|
CIMGUI_DEFINE_ENUMS_AND_STRUCTS=1
|
||||||
|
)
|
||||||
|
target_include_directories(spice-clipboard-tests PRIVATE
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/../src"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/../transports/SPICE"
|
||||||
|
"${PROJECT_TOP}/repos/PureSpice/include"
|
||||||
|
"${PROJECT_TOP}/repos/gui/cimgui"
|
||||||
|
"${PROJECT_TOP}/repos/gui/cimgui/imgui"
|
||||||
|
)
|
||||||
|
target_link_libraries(spice-clipboard-tests
|
||||||
|
${EXE_FLAGS}
|
||||||
|
lg_common
|
||||||
|
)
|
||||||
|
foreach(name IN ITEMS outbound inbound)
|
||||||
|
add_test(NAME spice-clipboard-${name}
|
||||||
|
COMMAND spice-clipboard-tests ${name}
|
||||||
|
)
|
||||||
|
set_tests_properties(spice-clipboard-${name} PROPERTIES
|
||||||
|
TIMEOUT 10
|
||||||
|
)
|
||||||
|
endforeach()
|
||||||
|
|
||||||
add_executable(transport-fallback-tests
|
add_executable(transport-fallback-tests
|
||||||
transport_fallback_test.c
|
transport_fallback_test.c
|
||||||
../src/transport_fallback.c
|
../src/transport_fallback.c
|
||||||
|
|||||||
@@ -54,6 +54,11 @@ struct Provider
|
|||||||
unsigned int release;
|
unsigned int release;
|
||||||
unsigned int notice;
|
unsigned int notice;
|
||||||
unsigned int data;
|
unsigned int data;
|
||||||
|
unsigned int begin;
|
||||||
|
unsigned int chunk;
|
||||||
|
unsigned int end;
|
||||||
|
unsigned int cancel;
|
||||||
|
unsigned int ready;
|
||||||
unsigned int request;
|
unsigned int request;
|
||||||
LG_ClipboardData noticeTypes[LG_CLIPBOARD_DATA_NONE];
|
LG_ClipboardData noticeTypes[LG_CLIPBOARD_DATA_NONE];
|
||||||
size_t noticeCount;
|
size_t noticeCount;
|
||||||
@@ -63,6 +68,17 @@ struct Provider
|
|||||||
LG_ClipboardData dataType[MAX_CALL];
|
LG_ClipboardData dataType[MAX_CALL];
|
||||||
size_t dataSize[MAX_CALL];
|
size_t dataSize[MAX_CALL];
|
||||||
uint8_t dataBuf[MAX_CALL][MAX_DATA];
|
uint8_t dataBuf[MAX_CALL][MAX_DATA];
|
||||||
|
LG_ClipboardResult beginResult;
|
||||||
|
LG_ClipboardResult chunkResult;
|
||||||
|
LG_ClipboardResult endResult;
|
||||||
|
LG_ClipboardRequest streamId;
|
||||||
|
LG_ClipboardData streamType;
|
||||||
|
uint64_t sizeHint;
|
||||||
|
uint64_t chunkOffset[MAX_CALL];
|
||||||
|
size_t chunkSize[MAX_CALL];
|
||||||
|
uint8_t chunkBuf[MAX_CALL][MAX_DATA];
|
||||||
|
uint64_t finalSize;
|
||||||
|
LG_ClipboardCancelReason cancelReason;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Display
|
struct Display
|
||||||
@@ -70,6 +86,8 @@ struct Display
|
|||||||
unsigned int notice;
|
unsigned int notice;
|
||||||
unsigned int release;
|
unsigned int release;
|
||||||
unsigned int request;
|
unsigned int request;
|
||||||
|
unsigned int ready;
|
||||||
|
unsigned int cancel;
|
||||||
LG_ClipboardData noticeType[MAX_CALL];
|
LG_ClipboardData noticeType[MAX_CALL];
|
||||||
LG_ClipboardRequest reqId[MAX_CALL];
|
LG_ClipboardRequest reqId[MAX_CALL];
|
||||||
LG_ClipboardData reqType[MAX_CALL];
|
LG_ClipboardData reqType[MAX_CALL];
|
||||||
@@ -77,6 +95,9 @@ struct Display
|
|||||||
LG_ClipboardData autoType;
|
LG_ClipboardData autoType;
|
||||||
const void * autoBuf;
|
const void * autoBuf;
|
||||||
size_t autoSize;
|
size_t autoSize;
|
||||||
|
bool autoBegin;
|
||||||
|
LG_ClipboardResult readyResult;
|
||||||
|
LG_ClipboardCancelReason cancelReason;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Reply
|
struct Reply
|
||||||
@@ -89,6 +110,24 @@ struct Reply
|
|||||||
bool nested;
|
bool nested;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct StreamSink
|
||||||
|
{
|
||||||
|
unsigned int begin;
|
||||||
|
unsigned int chunk;
|
||||||
|
unsigned int end;
|
||||||
|
unsigned int cancel;
|
||||||
|
LG_ClipboardResult beginResult;
|
||||||
|
LG_ClipboardResult chunkResult;
|
||||||
|
LG_ClipboardResult endResult;
|
||||||
|
LG_ClipboardData type;
|
||||||
|
uint64_t sizeHint;
|
||||||
|
uint64_t offset;
|
||||||
|
uint64_t finalSize;
|
||||||
|
LG_ClipboardCancelReason reason;
|
||||||
|
size_t size;
|
||||||
|
uint8_t data[MAX_DATA];
|
||||||
|
};
|
||||||
|
|
||||||
static struct Provider p;
|
static struct Provider p;
|
||||||
static struct Provider q;
|
static struct Provider q;
|
||||||
static struct Provider r;
|
static struct Provider r;
|
||||||
@@ -97,6 +136,61 @@ static struct Display d;
|
|||||||
struct AppState g_state;
|
struct AppState g_state;
|
||||||
struct AppParams g_params;
|
struct AppParams g_params;
|
||||||
|
|
||||||
|
static LG_ClipboardResult sinkBegin(void * opaque,
|
||||||
|
LG_ClipboardData type, uint64_t sizeHint)
|
||||||
|
{
|
||||||
|
struct StreamSink * sink = opaque;
|
||||||
|
++sink->begin;
|
||||||
|
sink->type = type;
|
||||||
|
sink->sizeHint = sizeHint;
|
||||||
|
return sink->beginResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
static LG_ClipboardResult sinkChunk(void * opaque, uint64_t offset,
|
||||||
|
const void * data, size_t size)
|
||||||
|
{
|
||||||
|
struct StreamSink * sink = opaque;
|
||||||
|
++sink->chunk;
|
||||||
|
sink->offset = offset;
|
||||||
|
if (sink->chunkResult == LG_CLIPBOARD_RESULT_ACCEPTED)
|
||||||
|
{
|
||||||
|
CHECK(offset + size <= MAX_DATA);
|
||||||
|
memcpy(sink->data + offset, data, size);
|
||||||
|
sink->size = offset + size;
|
||||||
|
}
|
||||||
|
return sink->chunkResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
static LG_ClipboardResult sinkEnd(void * opaque, uint64_t finalSize)
|
||||||
|
{
|
||||||
|
struct StreamSink * sink = opaque;
|
||||||
|
++sink->end;
|
||||||
|
sink->finalSize = finalSize;
|
||||||
|
return sink->endResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sinkCancel(void * opaque, LG_ClipboardCancelReason reason)
|
||||||
|
{
|
||||||
|
struct StreamSink * sink = opaque;
|
||||||
|
++sink->cancel;
|
||||||
|
sink->reason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const LG_ClipboardStreamOps sinkOps =
|
||||||
|
{
|
||||||
|
.begin = sinkBegin,
|
||||||
|
.chunk = sinkChunk,
|
||||||
|
.end = sinkEnd,
|
||||||
|
.cancel = sinkCancel,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void initSink(struct StreamSink * sink)
|
||||||
|
{
|
||||||
|
sink->beginResult = LG_CLIPBOARD_RESULT_ACCEPTED;
|
||||||
|
sink->chunkResult = LG_CLIPBOARD_RESULT_ACCEPTED;
|
||||||
|
sink->endResult = LG_CLIPBOARD_RESULT_ACCEPTED;
|
||||||
|
}
|
||||||
|
|
||||||
static void setStat(void * opaque, LG_ClipboardStatusFn callback,
|
static void setStat(void * opaque, LG_ClipboardStatusFn callback,
|
||||||
void * callbackOpaque)
|
void * callbackOpaque)
|
||||||
{
|
{
|
||||||
@@ -175,6 +269,59 @@ static bool data(void * opaque, LG_ClipboardRequest id,
|
|||||||
return provider->callOK;
|
return provider->callOK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static LG_ClipboardResult dataBegin(void * opaque,
|
||||||
|
LG_ClipboardRequest id, LG_ClipboardData type, uint64_t sizeHint)
|
||||||
|
{
|
||||||
|
struct Provider * provider = opaque;
|
||||||
|
++provider->begin;
|
||||||
|
provider->streamId = id;
|
||||||
|
provider->streamType = type;
|
||||||
|
provider->sizeHint = sizeHint;
|
||||||
|
return provider->beginResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
static LG_ClipboardResult dataChunk(void * opaque,
|
||||||
|
LG_ClipboardRequest id, uint64_t offset, const void * buf, size_t size)
|
||||||
|
{
|
||||||
|
struct Provider * provider = opaque;
|
||||||
|
CHECK(provider->chunk < MAX_CALL);
|
||||||
|
CHECK(id == provider->streamId);
|
||||||
|
const unsigned int no = provider->chunk++;
|
||||||
|
provider->chunkOffset[no] = offset;
|
||||||
|
provider->chunkSize[no] = size;
|
||||||
|
if (size <= MAX_DATA)
|
||||||
|
memcpy(provider->chunkBuf[no], buf, size);
|
||||||
|
return provider->chunkResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
static LG_ClipboardResult dataEnd(void * opaque,
|
||||||
|
LG_ClipboardRequest id, uint64_t finalSize)
|
||||||
|
{
|
||||||
|
struct Provider * provider = opaque;
|
||||||
|
CHECK(id == provider->streamId);
|
||||||
|
++provider->end;
|
||||||
|
provider->finalSize = finalSize;
|
||||||
|
return provider->endResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool dataCancel(void * opaque, LG_ClipboardRequest id,
|
||||||
|
LG_ClipboardCancelReason reason)
|
||||||
|
{
|
||||||
|
struct Provider * provider = opaque;
|
||||||
|
++provider->cancel;
|
||||||
|
provider->streamId = id;
|
||||||
|
provider->cancelReason = reason;
|
||||||
|
return provider->callOK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool dataReady(void * opaque, LG_ClipboardRequest id)
|
||||||
|
{
|
||||||
|
struct Provider * provider = opaque;
|
||||||
|
++provider->ready;
|
||||||
|
provider->streamId = id;
|
||||||
|
return provider->callOK;
|
||||||
|
}
|
||||||
|
|
||||||
static bool request(void * opaque, LG_ClipboardRequest id,
|
static bool request(void * opaque, LG_ClipboardRequest id,
|
||||||
LG_ClipboardData type)
|
LG_ClipboardData type)
|
||||||
{
|
{
|
||||||
@@ -209,6 +356,21 @@ static const LG_ClipboardOps statOps =
|
|||||||
.request = request,
|
.request = request,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const LG_ClipboardOps streamOps =
|
||||||
|
{
|
||||||
|
.name = "stream",
|
||||||
|
.attach = attach,
|
||||||
|
.detach = detach,
|
||||||
|
.release = release,
|
||||||
|
.notifyTypes = notify,
|
||||||
|
.dataBegin = dataBegin,
|
||||||
|
.dataChunk = dataChunk,
|
||||||
|
.dataEnd = dataEnd,
|
||||||
|
.dataCancel = dataCancel,
|
||||||
|
.dataReady = dataReady,
|
||||||
|
.request = request,
|
||||||
|
};
|
||||||
|
|
||||||
static void dsNotice(LG_ClipboardData type)
|
static void dsNotice(LG_ClipboardData type)
|
||||||
{
|
{
|
||||||
CHECK(d.notice < MAX_CALL);
|
CHECK(d.notice < MAX_CALL);
|
||||||
@@ -230,11 +392,29 @@ static void dsRequest(LG_ClipboardRequest id, LG_ClipboardData type)
|
|||||||
lgClipboard_data(id, d.autoType, d.autoBuf, d.autoSize);
|
lgClipboard_data(id, d.autoType, d.autoBuf, d.autoSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void dsRequestReady(LG_ClipboardRequest id)
|
||||||
|
{
|
||||||
|
++d.ready;
|
||||||
|
if (d.autoBegin)
|
||||||
|
d.readyResult = lgClipboard_dataBegin(
|
||||||
|
id, LG_CLIPBOARD_DATA_TEXT, LG_CLIPBOARD_SIZE_UNKNOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dsRequestCancel(LG_ClipboardRequest id,
|
||||||
|
LG_ClipboardCancelReason reason)
|
||||||
|
{
|
||||||
|
(void)id;
|
||||||
|
++d.cancel;
|
||||||
|
d.cancelReason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
static struct LG_DisplayServerOps dsOps =
|
static struct LG_DisplayServerOps dsOps =
|
||||||
{
|
{
|
||||||
.cbNotice = dsNotice,
|
.cbNotice = dsNotice,
|
||||||
.cbRelease = dsRelease,
|
.cbRelease = dsRelease,
|
||||||
.cbRequest = dsRequest,
|
.cbRequest = dsRequest,
|
||||||
|
.cbRequestReady = dsRequestReady,
|
||||||
|
.cbRequestCancel = dsRequestCancel,
|
||||||
};
|
};
|
||||||
|
|
||||||
static void initProvider(struct Provider * provider)
|
static void initProvider(struct Provider * provider)
|
||||||
@@ -244,6 +424,9 @@ static void initProvider(struct Provider * provider)
|
|||||||
provider->callOK = true;
|
provider->callOK = true;
|
||||||
provider->reqOK = true;
|
provider->reqOK = true;
|
||||||
provider->gen = 1;
|
provider->gen = 1;
|
||||||
|
provider->beginResult = LG_CLIPBOARD_RESULT_ACCEPTED;
|
||||||
|
provider->chunkResult = LG_CLIPBOARD_RESULT_ACCEPTED;
|
||||||
|
provider->endResult = LG_CLIPBOARD_RESULT_ACCEPTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void init(void)
|
static void init(void)
|
||||||
@@ -634,6 +817,192 @@ static void testReentrant(void)
|
|||||||
lgClipboard_free();
|
lgClipboard_free();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void testStreamLocal(void)
|
||||||
|
{
|
||||||
|
init();
|
||||||
|
lgClipboard_setFallback(&streamOps, &p);
|
||||||
|
const LG_ClipboardData types[] = { LG_CLIPBOARD_DATA_TEXT };
|
||||||
|
lgClipboard_notifyTypes(types, 1);
|
||||||
|
|
||||||
|
CHECK(p.ev->request(p.evCtx, 40, LG_CLIPBOARD_DATA_TEXT));
|
||||||
|
const LG_ClipboardRequest transfer = d.reqId[0];
|
||||||
|
CHECK(lgClipboard_dataBegin(transfer, LG_CLIPBOARD_DATA_TEXT,
|
||||||
|
LG_CLIPBOARD_SIZE_UNKNOWN) == LG_CLIPBOARD_RESULT_ACCEPTED);
|
||||||
|
const uint8_t first[] = { 1, 2 };
|
||||||
|
const uint8_t second[] = { 3, 4, 5 };
|
||||||
|
CHECK(lgClipboard_dataChunk(transfer, 0, first, sizeof(first)) ==
|
||||||
|
LG_CLIPBOARD_RESULT_ACCEPTED);
|
||||||
|
CHECK(lgClipboard_dataChunk(transfer, sizeof(first),
|
||||||
|
second, sizeof(second)) == LG_CLIPBOARD_RESULT_ACCEPTED);
|
||||||
|
CHECK(lgClipboard_dataEnd(transfer,
|
||||||
|
sizeof(first) + sizeof(second)) == LG_CLIPBOARD_RESULT_ACCEPTED);
|
||||||
|
CHECK(p.begin == 1);
|
||||||
|
CHECK(p.streamId == 40);
|
||||||
|
CHECK(p.streamType == LG_CLIPBOARD_DATA_TEXT);
|
||||||
|
CHECK(p.sizeHint == LG_CLIPBOARD_SIZE_UNKNOWN);
|
||||||
|
CHECK(p.chunk == 2);
|
||||||
|
CHECK(p.chunkOffset[0] == 0);
|
||||||
|
CHECK(p.chunkOffset[1] == sizeof(first));
|
||||||
|
CHECK(p.finalSize == sizeof(first) + sizeof(second));
|
||||||
|
|
||||||
|
CHECK(p.ev->request(p.evCtx, 41, LG_CLIPBOARD_DATA_TEXT));
|
||||||
|
const LG_ClipboardRequest invalid = d.reqId[1];
|
||||||
|
CHECK(lgClipboard_dataBegin(invalid, LG_CLIPBOARD_DATA_TEXT, 1) ==
|
||||||
|
LG_CLIPBOARD_RESULT_ACCEPTED);
|
||||||
|
CHECK(lgClipboard_dataChunk(invalid, 1, first, 1) ==
|
||||||
|
LG_CLIPBOARD_RESULT_FAILED);
|
||||||
|
CHECK(p.cancel == 1);
|
||||||
|
CHECK(p.streamId == 41);
|
||||||
|
CHECK(p.cancelReason == LG_CLIPBOARD_CANCEL_INVALID);
|
||||||
|
|
||||||
|
CHECK(p.ev->request(p.evCtx, 42, LG_CLIPBOARD_DATA_TEXT));
|
||||||
|
p.ev->requestCancel(p.evCtx, 42, LG_CLIPBOARD_CANCEL_REPLACED);
|
||||||
|
CHECK(d.cancel == 1);
|
||||||
|
CHECK(d.cancelReason == LG_CLIPBOARD_CANCEL_REPLACED);
|
||||||
|
|
||||||
|
lgClipboard_free();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testStreamBlocked(void)
|
||||||
|
{
|
||||||
|
init();
|
||||||
|
lgClipboard_setFallback(&streamOps, &p);
|
||||||
|
const LG_ClipboardData types[] = { LG_CLIPBOARD_DATA_TEXT };
|
||||||
|
lgClipboard_notifyTypes(types, 1);
|
||||||
|
CHECK(p.ev->request(p.evCtx, 50, LG_CLIPBOARD_DATA_TEXT));
|
||||||
|
|
||||||
|
p.beginResult = LG_CLIPBOARD_RESULT_BLOCKED;
|
||||||
|
uint8_t data[] = { 7, 8, 9 };
|
||||||
|
lgClipboard_data(d.reqId[0], LG_CLIPBOARD_DATA_TEXT,
|
||||||
|
data, sizeof(data));
|
||||||
|
CHECK(p.begin == 1);
|
||||||
|
CHECK(p.chunk == 0);
|
||||||
|
CHECK(p.end == 0);
|
||||||
|
data[0] = 0;
|
||||||
|
|
||||||
|
p.beginResult = LG_CLIPBOARD_RESULT_ACCEPTED;
|
||||||
|
p.ev->dataReady(p.evCtx, 50);
|
||||||
|
CHECK(p.begin == 2);
|
||||||
|
CHECK(p.chunk == 1);
|
||||||
|
CHECK(p.end == 1);
|
||||||
|
CHECK(p.chunkSize[0] == 3);
|
||||||
|
CHECK(p.chunkBuf[0][0] == 7);
|
||||||
|
CHECK(p.chunkBuf[0][1] == 8);
|
||||||
|
CHECK(p.chunkBuf[0][2] == 9);
|
||||||
|
CHECK(p.finalSize == 3);
|
||||||
|
|
||||||
|
CHECK(p.ev->request(p.evCtx, 51, LG_CLIPBOARD_DATA_TEXT));
|
||||||
|
const LG_ClipboardRequest transfer = d.reqId[1];
|
||||||
|
p.beginResult = LG_CLIPBOARD_RESULT_BLOCKED;
|
||||||
|
CHECK(lgClipboard_dataBegin(transfer, LG_CLIPBOARD_DATA_TEXT,
|
||||||
|
LG_CLIPBOARD_SIZE_UNKNOWN) == LG_CLIPBOARD_RESULT_BLOCKED);
|
||||||
|
p.beginResult = LG_CLIPBOARD_RESULT_ACCEPTED;
|
||||||
|
d.autoBegin = true;
|
||||||
|
p.ev->dataReady(p.evCtx, 51);
|
||||||
|
CHECK(d.ready == 1);
|
||||||
|
CHECK(d.readyResult == LG_CLIPBOARD_RESULT_ACCEPTED);
|
||||||
|
CHECK(lgClipboard_dataEnd(transfer, 0) ==
|
||||||
|
LG_CLIPBOARD_RESULT_ACCEPTED);
|
||||||
|
|
||||||
|
lgClipboard_free();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testStreamRemote(void)
|
||||||
|
{
|
||||||
|
init();
|
||||||
|
lgClipboard_setFallback(&streamOps, &p);
|
||||||
|
const LG_ClipboardData types[] = { LG_CLIPBOARD_DATA_TEXT };
|
||||||
|
notice(&p, types, 1);
|
||||||
|
|
||||||
|
struct StreamSink sink = { 0 };
|
||||||
|
initSink(&sink);
|
||||||
|
LG_ClipboardRequest id;
|
||||||
|
CHECK(lgClipboard_requestStream(
|
||||||
|
LG_CLIPBOARD_DATA_TEXT, &sinkOps, &sink, &id));
|
||||||
|
CHECK(id == p.reqId[0]);
|
||||||
|
CHECK(p.ev->dataBegin(p.evCtx, id, LG_CLIPBOARD_DATA_TEXT,
|
||||||
|
LG_CLIPBOARD_SIZE_UNKNOWN) == LG_CLIPBOARD_RESULT_ACCEPTED);
|
||||||
|
CHECK(sink.begin == 1);
|
||||||
|
CHECK(sink.sizeHint == LG_CLIPBOARD_SIZE_UNKNOWN);
|
||||||
|
|
||||||
|
const uint8_t data[] = { 1, 3, 5, 7 };
|
||||||
|
sink.chunkResult = LG_CLIPBOARD_RESULT_BLOCKED;
|
||||||
|
CHECK(p.ev->dataChunk(p.evCtx, id, 0, data, sizeof(data)) ==
|
||||||
|
LG_CLIPBOARD_RESULT_BLOCKED);
|
||||||
|
CHECK(sink.chunk == 1);
|
||||||
|
sink.chunkResult = LG_CLIPBOARD_RESULT_ACCEPTED;
|
||||||
|
CHECK(lgClipboard_requestReady(id));
|
||||||
|
CHECK(p.ready == 1);
|
||||||
|
CHECK(p.streamId == id);
|
||||||
|
CHECK(p.ev->dataChunk(p.evCtx, id, 0, data, sizeof(data)) ==
|
||||||
|
LG_CLIPBOARD_RESULT_ACCEPTED);
|
||||||
|
CHECK(p.ev->dataEnd(p.evCtx, id, sizeof(data)) ==
|
||||||
|
LG_CLIPBOARD_RESULT_ACCEPTED);
|
||||||
|
CHECK(sink.chunk == 2);
|
||||||
|
CHECK(sink.end == 1);
|
||||||
|
CHECK(sink.finalSize == sizeof(data));
|
||||||
|
CHECK(memcmp(sink.data, data, sizeof(data)) == 0);
|
||||||
|
|
||||||
|
struct StreamSink cancel = { 0 };
|
||||||
|
initSink(&cancel);
|
||||||
|
CHECK(lgClipboard_requestStream(
|
||||||
|
LG_CLIPBOARD_DATA_TEXT, &sinkOps, &cancel, &id));
|
||||||
|
p.ev->dataCancel(p.evCtx, id, LG_CLIPBOARD_CANCEL_ABORTED);
|
||||||
|
CHECK(cancel.cancel == 1);
|
||||||
|
CHECK(cancel.reason == LG_CLIPBOARD_CANCEL_ABORTED);
|
||||||
|
|
||||||
|
lgClipboard_free();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testStreamLegacy(void)
|
||||||
|
{
|
||||||
|
init();
|
||||||
|
bind(&p);
|
||||||
|
const LG_ClipboardData types[] = { LG_CLIPBOARD_DATA_TEXT };
|
||||||
|
lgClipboard_notifyTypes(types, 1);
|
||||||
|
|
||||||
|
CHECK(p.ev->request(p.evCtx, 60, LG_CLIPBOARD_DATA_TEXT));
|
||||||
|
const LG_ClipboardRequest transfer = d.reqId[0];
|
||||||
|
const uint8_t first[] = { 2, 4 };
|
||||||
|
const uint8_t second[] = { 6, 8 };
|
||||||
|
CHECK(lgClipboard_dataBegin(transfer, LG_CLIPBOARD_DATA_TEXT,
|
||||||
|
LG_CLIPBOARD_SIZE_UNKNOWN) == LG_CLIPBOARD_RESULT_ACCEPTED);
|
||||||
|
CHECK(lgClipboard_dataChunk(transfer, 0, first, sizeof(first)) ==
|
||||||
|
LG_CLIPBOARD_RESULT_ACCEPTED);
|
||||||
|
CHECK(lgClipboard_dataChunk(transfer, sizeof(first), second,
|
||||||
|
sizeof(second)) == LG_CLIPBOARD_RESULT_ACCEPTED);
|
||||||
|
CHECK(lgClipboard_dataEnd(transfer, 4) ==
|
||||||
|
LG_CLIPBOARD_RESULT_ACCEPTED);
|
||||||
|
CHECK(p.data == 1);
|
||||||
|
CHECK(p.dataId[0] == 60);
|
||||||
|
CHECK(p.dataType[0] == LG_CLIPBOARD_DATA_TEXT);
|
||||||
|
CHECK(p.dataSize[0] == 4);
|
||||||
|
CHECK(memcmp(p.dataBuf[0], "\x02\x04\x06\x08", 4) == 0);
|
||||||
|
|
||||||
|
notice(&p, types, 1);
|
||||||
|
struct StreamSink sink = { 0 };
|
||||||
|
initSink(&sink);
|
||||||
|
sink.beginResult = LG_CLIPBOARD_RESULT_BLOCKED;
|
||||||
|
LG_ClipboardRequest id;
|
||||||
|
CHECK(lgClipboard_requestStream(
|
||||||
|
LG_CLIPBOARD_DATA_TEXT, &sinkOps, &sink, &id));
|
||||||
|
const uint8_t remote[] = { 9, 8, 7 };
|
||||||
|
remoteData(&p, id, LG_CLIPBOARD_DATA_TEXT, remote, sizeof(remote));
|
||||||
|
CHECK(sink.begin == 1);
|
||||||
|
CHECK(sink.chunk == 0);
|
||||||
|
|
||||||
|
sink.beginResult = LG_CLIPBOARD_RESULT_ACCEPTED;
|
||||||
|
CHECK(lgClipboard_requestReady(id));
|
||||||
|
CHECK(p.ready == 0);
|
||||||
|
CHECK(sink.begin == 2);
|
||||||
|
CHECK(sink.chunk == 1);
|
||||||
|
CHECK(sink.end == 1);
|
||||||
|
CHECK(sink.size == sizeof(remote));
|
||||||
|
CHECK(memcmp(sink.data, remote, sizeof(remote)) == 0);
|
||||||
|
|
||||||
|
lgClipboard_free();
|
||||||
|
}
|
||||||
|
|
||||||
struct Test
|
struct Test
|
||||||
{
|
{
|
||||||
const char * name;
|
const char * name;
|
||||||
@@ -649,6 +1018,10 @@ static const struct Test tests[] =
|
|||||||
{ "generation", testGeneration },
|
{ "generation", testGeneration },
|
||||||
{ "local" , testLocal },
|
{ "local" , testLocal },
|
||||||
{ "reentrant" , testReentrant },
|
{ "reentrant" , testReentrant },
|
||||||
|
{ "stream-local" , testStreamLocal },
|
||||||
|
{ "stream-blocked", testStreamBlocked },
|
||||||
|
{ "stream-remote" , testStreamRemote },
|
||||||
|
{ "stream-legacy" , testStreamLegacy },
|
||||||
};
|
};
|
||||||
|
|
||||||
int main(int argc, char ** argv)
|
int main(int argc, char ** argv)
|
||||||
|
|||||||
238
client/tests/spice_clipboard_test.c
Normal file
238
client/tests/spice_clipboard_test.c
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
/**
|
||||||
|
* 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 "../src/clipboard.h"
|
||||||
|
#include "../src/main.h"
|
||||||
|
#include "../transports/SPICE/clipboard.h"
|
||||||
|
#include "test.h"
|
||||||
|
|
||||||
|
#include "common/debug.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define MAX_DATA 32U
|
||||||
|
#define TEST_ALARM_S 5U
|
||||||
|
|
||||||
|
struct AppState g_state;
|
||||||
|
struct AppParams g_params;
|
||||||
|
|
||||||
|
static struct
|
||||||
|
{
|
||||||
|
unsigned int grab;
|
||||||
|
unsigned int request;
|
||||||
|
unsigned int dataStart;
|
||||||
|
unsigned int data;
|
||||||
|
PSDataType requestType;
|
||||||
|
PSDataType dataType;
|
||||||
|
size_t dataSize;
|
||||||
|
uint8_t dataBuf[MAX_DATA];
|
||||||
|
|
||||||
|
unsigned int notice;
|
||||||
|
LG_ClipboardRequest localRequest;
|
||||||
|
|
||||||
|
unsigned int reply;
|
||||||
|
LG_ClipboardData replyType;
|
||||||
|
uint32_t replySize;
|
||||||
|
uint8_t replyBuf[MAX_DATA];
|
||||||
|
}
|
||||||
|
test;
|
||||||
|
|
||||||
|
bool purespice_clipboardRequest(PSDataType type)
|
||||||
|
{
|
||||||
|
++test.request;
|
||||||
|
test.requestType = type;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool purespice_clipboardGrab(PSDataType types[], int count)
|
||||||
|
{
|
||||||
|
CHECK(types);
|
||||||
|
CHECK(count > 0);
|
||||||
|
++test.grab;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool purespice_clipboardRelease(void)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool purespice_clipboardDataStart(PSDataType type, size_t size)
|
||||||
|
{
|
||||||
|
++test.dataStart;
|
||||||
|
test.dataType = type;
|
||||||
|
test.dataSize = size;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool purespice_clipboardData(
|
||||||
|
PSDataType type, uint8_t * data, size_t size)
|
||||||
|
{
|
||||||
|
CHECK(type == test.dataType);
|
||||||
|
CHECK(size == test.dataSize);
|
||||||
|
CHECK(size <= sizeof(test.dataBuf));
|
||||||
|
++test.data;
|
||||||
|
if (size)
|
||||||
|
memcpy(test.dataBuf, data, size);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cbNotice(LG_ClipboardData type)
|
||||||
|
{
|
||||||
|
CHECK(type == LG_CLIPBOARD_DATA_TEXT);
|
||||||
|
++test.notice;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cbRelease(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cbRequest(
|
||||||
|
LG_ClipboardRequest request, LG_ClipboardData type)
|
||||||
|
{
|
||||||
|
CHECK(type == LG_CLIPBOARD_DATA_TEXT);
|
||||||
|
test.localRequest = request;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct LG_DisplayServerOps displayOps =
|
||||||
|
{
|
||||||
|
.cbNotice = cbNotice,
|
||||||
|
.cbRelease = cbRelease,
|
||||||
|
.cbRequest = cbRequest,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void reply(void * opaque, LG_ClipboardData type,
|
||||||
|
const uint8_t * data, uint32_t size)
|
||||||
|
{
|
||||||
|
(void)opaque;
|
||||||
|
++test.reply;
|
||||||
|
test.replyType = type;
|
||||||
|
test.replySize = size;
|
||||||
|
CHECK(size <= sizeof(test.replyBuf));
|
||||||
|
if (size)
|
||||||
|
memcpy(test.replyBuf, data, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static SpiceClipboard * setup(void)
|
||||||
|
{
|
||||||
|
SpiceClipboard * clipboard;
|
||||||
|
CHECK(spiceClipboard_init(&clipboard));
|
||||||
|
spiceClipboard_setCallbackTarget(clipboard);
|
||||||
|
spiceClipboard_setAvailable(clipboard, true);
|
||||||
|
|
||||||
|
g_state.ds = &displayOps;
|
||||||
|
g_params.clipboardToVM = true;
|
||||||
|
g_params.clipboardToLocal = true;
|
||||||
|
lgClipboard_init();
|
||||||
|
lgClipboard_setLocalAvailable(true);
|
||||||
|
lgClipboard_setFallback(spiceClipboard_getOps(), clipboard);
|
||||||
|
return clipboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void teardown(SpiceClipboard ** clipboard)
|
||||||
|
{
|
||||||
|
lgClipboard_free();
|
||||||
|
spiceClipboard_setCallbackTarget(NULL);
|
||||||
|
spiceClipboard_free(clipboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testOutbound(void)
|
||||||
|
{
|
||||||
|
SpiceClipboard * clipboard = setup();
|
||||||
|
const LG_ClipboardData types[] = { LG_CLIPBOARD_DATA_TEXT };
|
||||||
|
lgClipboard_notifyTypes(types, 1);
|
||||||
|
CHECK(test.grab == 1);
|
||||||
|
|
||||||
|
spiceClipboard_request(SPICE_DATA_TEXT);
|
||||||
|
CHECK(test.localRequest != LG_CLIPBOARD_REQUEST_INVALID);
|
||||||
|
const uint8_t first[] = { 1, 2 };
|
||||||
|
const uint8_t second[] = { 3, 4 };
|
||||||
|
CHECK(lgClipboard_dataBegin(test.localRequest,
|
||||||
|
LG_CLIPBOARD_DATA_TEXT, LG_CLIPBOARD_SIZE_UNKNOWN) ==
|
||||||
|
LG_CLIPBOARD_RESULT_ACCEPTED);
|
||||||
|
CHECK(lgClipboard_dataChunk(test.localRequest,
|
||||||
|
0, first, sizeof(first)) == LG_CLIPBOARD_RESULT_ACCEPTED);
|
||||||
|
CHECK(lgClipboard_dataChunk(test.localRequest,
|
||||||
|
sizeof(first), second, sizeof(second)) ==
|
||||||
|
LG_CLIPBOARD_RESULT_ACCEPTED);
|
||||||
|
CHECK(lgClipboard_dataEnd(test.localRequest, 4) ==
|
||||||
|
LG_CLIPBOARD_RESULT_ACCEPTED);
|
||||||
|
|
||||||
|
CHECK(test.dataStart == 1);
|
||||||
|
CHECK(test.data == 1);
|
||||||
|
CHECK(test.dataType == SPICE_DATA_TEXT);
|
||||||
|
CHECK(test.dataSize == 4);
|
||||||
|
CHECK(memcmp(test.dataBuf, "\x01\x02\x03\x04", 4) == 0);
|
||||||
|
teardown(&clipboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testInbound(void)
|
||||||
|
{
|
||||||
|
SpiceClipboard * clipboard = setup();
|
||||||
|
spiceClipboard_notice(SPICE_DATA_TEXT);
|
||||||
|
CHECK(test.notice == 1);
|
||||||
|
CHECK(lgClipboard_request(LG_CLIPBOARD_DATA_TEXT, reply, NULL));
|
||||||
|
CHECK(test.request == 1);
|
||||||
|
CHECK(test.requestType == SPICE_DATA_TEXT);
|
||||||
|
|
||||||
|
uint8_t data[] = "one\r\ntwo";
|
||||||
|
spiceClipboard_data(SPICE_DATA_TEXT, data, sizeof(data) - 1);
|
||||||
|
CHECK(test.reply == 1);
|
||||||
|
CHECK(test.replyType == LG_CLIPBOARD_DATA_TEXT);
|
||||||
|
CHECK(test.replySize == 7);
|
||||||
|
CHECK(memcmp(test.replyBuf, "one\ntwo", 7) == 0);
|
||||||
|
teardown(&clipboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Test
|
||||||
|
{
|
||||||
|
const char * name;
|
||||||
|
void (*run)(void);
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct Test tests[] =
|
||||||
|
{
|
||||||
|
{ "outbound", testOutbound },
|
||||||
|
{ "inbound" , testInbound },
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int argc, char ** argv)
|
||||||
|
{
|
||||||
|
if (argc != 2)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "usage: %s <case>\n", argv[0]);
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
alarm(TEST_ALARM_S);
|
||||||
|
debug_init();
|
||||||
|
for (unsigned int i = 0; i < sizeof(tests) / sizeof(tests[0]); ++i)
|
||||||
|
if (strcmp(argv[1], tests[i].name) == 0)
|
||||||
|
{
|
||||||
|
tests[i].run();
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "unknown test: %s\n", argv[1]);
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user