[client] clipboard: add direct LGMP synchronization

This commit is contained in:
Geoffrey McRae
2026-08-14 06:27:12 +10:00
parent b786167f72
commit be2f79a412
21 changed files with 5228 additions and 350 deletions

View File

@@ -103,6 +103,38 @@ foreach(name IN LISTS LGMP_INPUT_CASES)
)
endforeach()
add_executable(lgmp-clipboard-tests
lgmp_clipboard_test.c
../transports/LGMP/clipboard.c
)
target_include_directories(lgmp-clipboard-tests PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/../src"
)
target_link_libraries(lgmp-clipboard-tests
${EXE_FLAGS}
lg_common
lgmp
)
set(LGMP_CLIPBOARD_CASES
claim
offer-request
inbound-stream
outbound-grant
blocked
cancel
restart
disconnect
malformed
)
foreach(name IN LISTS LGMP_CLIPBOARD_CASES)
add_test(NAME lgmp-clipboard-${name}
COMMAND lgmp-clipboard-tests ${name}
)
set_tests_properties(lgmp-clipboard-${name} PROPERTIES
TIMEOUT 10
)
endforeach()
add_executable(wayland-motion-tests
motion_test.c
)
@@ -165,9 +197,19 @@ if(ENABLE_WAYLAND)
mime
replace
read-eof
read-block
read-empty
read-ready
read-error
source
source-release
source-external
source-replace
source-cancel
source-early
source-error
teardown
teardown-external
self-copy
dnd
)
@@ -192,14 +234,20 @@ if(ENABLE_X11)
target_link_libraries(x11-clipboard-tests
${EXE_FLAGS}
lg_common
pthread
)
set(X11_CLIPBOARD_CASES
targets
normal
incr
incr-block
incr-ready
incr-cancel
incr-large
malformed
replace
source
source-early
teardown
)
foreach(name IN LISTS X11_CLIPBOARD_CASES)
@@ -431,6 +479,7 @@ set(CLIPBOARD_CASES
stream-local
stream-blocked
stream-remote
request-publish
stream-legacy
)
foreach(name IN LISTS CLIPBOARD_CASES)

View File

@@ -24,6 +24,7 @@
#include "common/debug.h"
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
@@ -79,6 +80,8 @@ struct Provider
uint8_t chunkBuf[MAX_CALL][MAX_DATA];
uint64_t finalSize;
LG_ClipboardCancelReason cancelReason;
bool earlyBegin;
LG_ClipboardResult earlyResult;
};
struct Display
@@ -124,6 +127,8 @@ struct StreamSink
uint64_t offset;
uint64_t finalSize;
LG_ClipboardCancelReason reason;
LG_ClipboardRequest request;
bool requireRequest;
size_t size;
uint8_t data[MAX_DATA];
};
@@ -140,6 +145,8 @@ static LG_ClipboardResult sinkBegin(void * opaque,
LG_ClipboardData type, uint64_t sizeHint)
{
struct StreamSink * sink = opaque;
if (sink->requireRequest)
CHECK(sink->request != LG_CLIPBOARD_REQUEST_INVALID);
++sink->begin;
sink->type = type;
sink->sizeHint = sizeHint;
@@ -322,6 +329,22 @@ static bool dataReady(void * opaque, LG_ClipboardRequest id)
return provider->callOK;
}
struct EarlyRequest
{
struct Provider * provider;
LG_ClipboardRequest request;
LG_ClipboardData type;
};
static void * earlyRequestThread(void * opaque)
{
struct EarlyRequest * early = opaque;
early->provider->earlyResult = early->provider->ev->dataBegin(
early->provider->evCtx, early->request, early->type,
LG_CLIPBOARD_SIZE_UNKNOWN);
return NULL;
}
static bool request(void * opaque, LG_ClipboardRequest id,
LG_ClipboardData type)
{
@@ -330,6 +353,18 @@ static bool request(void * opaque, LG_ClipboardRequest id,
const unsigned int no = provider->request++;
provider->reqId[no] = id;
provider->reqType[no] = type;
if (provider->earlyBegin)
{
struct EarlyRequest early =
{
.provider = provider,
.request = id,
.type = type,
};
pthread_t thread;
CHECK(pthread_create(&thread, NULL, earlyRequestThread, &early) == 0);
CHECK(pthread_join(thread, NULL) == 0);
}
return provider->reqOK;
}
@@ -954,6 +989,29 @@ static void testStreamRemote(void)
lgClipboard_free();
}
static void testRequestPublication(void)
{
init();
lgClipboard_setFallback(&streamOps, &p);
const LG_ClipboardData types[] = { LG_CLIPBOARD_DATA_TEXT };
notice(&p, types, 1);
struct StreamSink sink = { 0 };
initSink(&sink);
sink.requireRequest = true;
p.earlyBegin = true;
CHECK(lgClipboard_requestStream(LG_CLIPBOARD_DATA_TEXT,
&sinkOps, &sink, &sink.request));
CHECK(sink.request == p.reqId[0]);
CHECK(p.earlyResult == LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(sink.begin == 1);
CHECK(p.ev->dataEnd(p.evCtx, sink.request, 0) ==
LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(sink.end == 1);
lgClipboard_free();
}
static void testStreamLegacy(void)
{
init();
@@ -1021,6 +1079,7 @@ static const struct Test tests[] =
{ "stream-local" , testStreamLocal },
{ "stream-blocked", testStreamBlocked },
{ "stream-remote" , testStreamRemote },
{ "request-publish", testRequestPublication },
{ "stream-legacy" , testStreamLegacy },
};

File diff suppressed because it is too large Load Diff

View File

@@ -20,6 +20,7 @@
#include "wayland.h"
#include "test.h"
#include "../src/clipboard.h"
#include "common/debug.h"
@@ -76,9 +77,12 @@ struct Proto
unsigned int dirtyDestroyN;
unsigned int receiveN;
unsigned int setActionsN;
unsigned int selectionSetN;
char receiveMime[80];
const void * receiveData;
size_t receiveSize;
int receiveFd;
bool keepReceiveOpen;
struct Proxy * selection;
uint32_t serial;
};
@@ -97,7 +101,7 @@ struct Poll
struct TypeNotice
{
LG_ClipboardData type[LG_CLIPBOARD_DATA_NONE];
int count;
size_t count;
};
struct DataNotice
@@ -108,11 +112,25 @@ struct DataNotice
size_t size;
};
struct StreamNotice
{
LG_ClipboardRequest request;
LG_ClipboardData type;
uint64_t sizeHint;
uint64_t finalSize;
uint8_t data[MAX_TRANSFER];
size_t size;
unsigned int beginN;
unsigned int chunkN;
unsigned int endN;
};
struct Log
{
struct Poll poll[MAX_POLL];
struct TypeNotice notice[MAX_NOTICE];
struct DataNotice data[MAX_DATA];
struct StreamNotice streamData;
LG_ClipboardRequest abort[MAX_ABORT];
unsigned int pollN;
unsigned int pollUnregisterN;
@@ -122,11 +140,21 @@ struct Log
unsigned int abortN;
unsigned int releaseN;
unsigned int requestN;
unsigned int requestReadyN;
LG_ClipboardData requestType;
LG_ClipboardReplyFn reply;
void * replyOpaque;
const LG_ClipboardStreamOps * requestStream;
void * requestOpaque;
LG_ClipboardRequest requestId;
LG_ClipboardResult beginResult;
LG_ClipboardResult chunkResult;
LG_ClipboardResult endResult;
LG_ClipboardResult requestBeginResult;
bool readyDuringChunk;
bool readyDuringEnd;
bool pollOK;
bool requestOK;
bool requestCancelSync;
bool requestBeginSync;
bool firing;
};
@@ -280,6 +308,7 @@ struct wl_proxy * wl_proxy_marshal_flags(struct wl_proxy * proxy,
CHECK(!interface);
proto.selection = (struct Proxy *)va_arg(ap, struct wl_data_source *);
proto.serial = va_arg(ap, unsigned int);
++proto.selectionSetN;
p = NULL;
break;
@@ -292,6 +321,12 @@ struct wl_proxy * wl_proxy_marshal_flags(struct wl_proxy * proxy,
++proto.receiveN;
snprintf(proto.receiveMime, sizeof(proto.receiveMime),
"%s", p->mime[p->mimeN - 1]);
if (proto.keepReceiveOpen)
{
CHECK(proto.receiveFd < 0);
proto.receiveFd = dup(fd);
CHECK(proto.receiveFd >= 0);
}
if (proto.receiveSize)
sendData(fd, proto.receiveData, proto.receiveSize);
}
@@ -364,6 +399,20 @@ bool waylandPollUnregister(int fd)
return false;
}
bool waylandPollUpdate(int fd, uint32_t events)
{
for (unsigned int i = 0; i < rec.pollN; ++i)
{
struct Poll * poll = &rec.poll[i];
if (!poll->active || poll->fd != fd)
continue;
poll->events = events;
return true;
}
return false;
}
static void pollFire(unsigned int no, uint32_t events)
{
CHECK(no < rec.pollN);
@@ -379,28 +428,28 @@ static void pollFire(unsigned int no, uint32_t events)
pollCleanup(&rec.poll[i]);
}
void app_clipboardNotifyTypes(const LG_ClipboardData types[], int count)
void lgClipboard_notifyTypes(
const LG_ClipboardData types[], size_t count)
{
CHECK(rec.noticeN < ARRAY_LENGTH(rec.notice));
CHECK(count >= 0);
CHECK(count <= LG_CLIPBOARD_DATA_NONE);
CHECK(count <= ARRAY_LENGTH(rec.notice[0].type));
struct TypeNotice * notice = &rec.notice[rec.noticeN++];
memcpy(notice->type, types, (size_t)count * sizeof(*types));
memcpy(notice->type, types, count * sizeof(*types));
notice->count = count;
}
void app_clipboardRelease(void)
void lgClipboard_release(void)
{
++rec.releaseN;
}
void app_clipboardAbort(LG_ClipboardRequest request)
void lgClipboard_abort(LG_ClipboardRequest request)
{
CHECK(rec.abortN < ARRAY_LENGTH(rec.abort));
rec.abort[rec.abortN++] = request;
}
void app_clipboardData(LG_ClipboardRequest request,
void lgClipboard_data(LG_ClipboardRequest request,
LG_ClipboardData type, const void * data, size_t size)
{
CHECK(rec.dataN < ARRAY_LENGTH(rec.data));
@@ -412,16 +461,93 @@ void app_clipboardData(LG_ClipboardRequest request,
memcpy(notice->data, data, size);
}
bool app_clipboardRequest(LG_ClipboardData type,
LG_ClipboardReplyFn replyFn, void * opaque)
LG_ClipboardResult lgClipboard_dataBegin(LG_ClipboardRequest request,
LG_ClipboardData type, uint64_t sizeHint)
{
struct StreamNotice * stream = &rec.streamData;
++stream->beginN;
stream->request = request;
stream->type = type;
stream->sizeHint = sizeHint;
return rec.beginResult;
}
LG_ClipboardResult lgClipboard_dataChunk(LG_ClipboardRequest request,
uint64_t offset, const void * data, size_t size)
{
const LG_ClipboardResult result = rec.chunkResult;
struct StreamNotice * stream = &rec.streamData;
++stream->chunkN;
CHECK(request == stream->request);
CHECK(offset == stream->size);
CHECK(data);
CHECK(size);
CHECK(size <= sizeof(stream->data) - stream->size);
if (rec.readyDuringChunk)
{
rec.readyDuringChunk = false;
rec.chunkResult = LG_CLIPBOARD_RESULT_ACCEPTED;
waylandCBRequestReady(request);
}
if (result == LG_CLIPBOARD_RESULT_ACCEPTED)
{
memcpy(stream->data + stream->size, data, size);
stream->size += size;
}
return result;
}
LG_ClipboardResult lgClipboard_dataEnd(LG_ClipboardRequest request,
uint64_t finalSize)
{
const LG_ClipboardResult result = rec.endResult;
struct StreamNotice * stream = &rec.streamData;
++stream->endN;
CHECK(request == stream->request);
stream->finalSize = finalSize;
if (rec.readyDuringEnd)
{
rec.readyDuringEnd = false;
rec.endResult = LG_CLIPBOARD_RESULT_ACCEPTED;
waylandCBRequestReady(request);
}
return result;
}
bool lgClipboard_requestStream(LG_ClipboardData type,
const LG_ClipboardStreamOps * stream, void * opaque,
LG_ClipboardRequest * request)
{
++rec.requestN;
rec.requestType = type;
rec.reply = replyFn;
rec.replyOpaque = opaque;
rec.requestStream = stream;
rec.requestOpaque = opaque;
rec.requestId = 9000 + rec.requestN;
if (request)
*request = rec.requestId;
if (rec.requestBeginSync)
{
rec.requestBeginResult = stream->begin(
opaque, type, LG_CLIPBOARD_SIZE_UNKNOWN);
const uint8_t byte = 0xa5;
CHECK(stream->chunk(opaque, 0, &byte, 1) ==
LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(stream->chunk(opaque, 1, &byte, 1) ==
LG_CLIPBOARD_RESULT_BLOCKED);
pollFire(rec.pollN - 1, EPOLLOUT);
}
if (rec.requestCancelSync)
stream->cancel(opaque, LG_CLIPBOARD_CANCEL_REPLACED);
return rec.requestOK;
}
bool lgClipboard_requestReady(LG_ClipboardRequest request)
{
CHECK(request == rec.requestId);
++rec.requestReadyN;
return true;
}
static const struct wl_data_device_listener * deviceListener(void)
{
CHECK(proto.device.listener);
@@ -458,6 +584,7 @@ static void offerMime(struct Proxy * offer, const char * mime)
static void selectOffer(struct Proxy * offer)
{
proto.selection = offer;
deviceListener()->selection(proto.device.data,
(struct wl_data_device *)&proto.device,
(struct wl_data_offer *)offer);
@@ -477,10 +604,14 @@ static void start(void)
memset(&wlCb, 0, sizeof(wlCb));
memset(&proto, 0, sizeof(proto));
memset(&rec, 0, sizeof(rec));
proto.receiveFd = -1;
LG_LOCK_INIT(wlCb.lock);
rec.pollOK = true;
rec.requestOK = true;
rec.beginResult = LG_CLIPBOARD_RESULT_ACCEPTED;
rec.chunkResult = LG_CLIPBOARD_RESULT_ACCEPTED;
rec.endResult = LG_CLIPBOARD_RESULT_ACCEPTED;
proto.manager.kind = PROXY_MANAGER;
proto.device.kind = PROXY_DEVICE;
wlWm.dataDeviceManager =
@@ -497,6 +628,11 @@ static void start(void)
static void finish(void)
{
if (proto.receiveFd >= 0)
{
CHECK(close(proto.receiveFd) == 0);
proto.receiveFd = -1;
}
waylandCBFree();
CHECK(!wlCb.dataDevice);
CHECK(!wlCb.offer);
@@ -620,22 +756,120 @@ static void testReadEOF(void)
pollFire(0, EPOLLIN);
CHECK(rec.poll[0].active);
CHECK(rec.dataN == 0);
CHECK(rec.streamData.beginN == 1);
CHECK(rec.streamData.chunkN == 1);
CHECK(rec.streamData.endN == 0);
pollFire(0, EPOLLIN);
CHECK(!rec.poll[0].active);
CHECK(rec.pollCleanupN == 1);
CHECK(rec.dataN == 1);
CHECK(rec.data[0].request == 201);
CHECK(rec.data[0].type == LG_CLIPBOARD_DATA_TEXT);
CHECK(rec.data[0].size == sizeof(data));
CHECK(memcmp(rec.data[0].data, data, sizeof(data)) == 0);
CHECK(rec.streamData.request == 201);
CHECK(rec.streamData.type == LG_CLIPBOARD_DATA_TEXT);
CHECK(rec.streamData.sizeHint == LG_CLIPBOARD_SIZE_UNKNOWN);
CHECK(rec.streamData.size == sizeof(data));
CHECK(rec.streamData.finalSize == sizeof(data));
CHECK(rec.streamData.endN == 1);
CHECK(memcmp(rec.streamData.data, data, sizeof(data)) == 0);
checkClosed(fd);
finish();
CHECK(rec.dataN == 1);
CHECK(rec.abortN == 0);
}
static void testReadBlocked(void)
{
start();
textOffer();
const uint8_t data[] = "blocked stream";
proto.receiveData = data;
proto.receiveSize = sizeof(data) - 1;
rec.chunkResult = LG_CLIPBOARD_RESULT_BLOCKED;
waylandCBRequest(202, LG_CLIPBOARD_DATA_TEXT);
CHECK(rec.pollN == 1);
pollFire(0, EPOLLIN);
CHECK(rec.streamData.beginN == 1);
CHECK(rec.streamData.chunkN == 1);
CHECK(rec.streamData.size == 0);
CHECK(rec.poll[0].events == 0);
rec.chunkResult = LG_CLIPBOARD_RESULT_ACCEPTED;
waylandCBRequestReady(202);
CHECK(rec.streamData.chunkN == 2);
CHECK(rec.streamData.size == sizeof(data) - 1);
CHECK(rec.streamData.endN == 1);
CHECK(rec.streamData.finalSize == sizeof(data) - 1);
CHECK(memcmp(rec.streamData.data, data, sizeof(data) - 1) == 0);
CHECK(!rec.poll[0].active);
CHECK(rec.abortN == 0);
finish();
}
static void testReadBlockedEmpty(void)
{
start();
textOffer();
const uint8_t data[] = "buffered while source stays open";
proto.receiveData = data;
proto.receiveSize = sizeof(data) - 1;
proto.keepReceiveOpen = true;
rec.chunkResult = LG_CLIPBOARD_RESULT_BLOCKED;
waylandCBRequest(204, LG_CLIPBOARD_DATA_TEXT);
CHECK(rec.pollN == 1);
pollFire(0, EPOLLIN);
CHECK(rec.streamData.chunkN == 1);
CHECK(rec.streamData.size == 0);
CHECK(rec.poll[0].events == 0);
/* The read drained the pipe, whose duplicated write end remains open.
* Ready must retry the buffered chunk without another poll callback. */
rec.chunkResult = LG_CLIPBOARD_RESULT_ACCEPTED;
waylandCBRequestReady(204);
CHECK(rec.streamData.chunkN == 2);
CHECK(rec.streamData.size == sizeof(data) - 1);
CHECK(memcmp(rec.streamData.data, data, sizeof(data) - 1) == 0);
CHECK(rec.streamData.endN == 0);
CHECK(rec.poll[0].active);
CHECK(rec.poll[0].events == EPOLLIN);
CHECK(close(proto.receiveFd) == 0);
proto.receiveFd = -1;
pollFire(0, EPOLLIN);
CHECK(rec.streamData.endN == 1);
CHECK(rec.streamData.finalSize == sizeof(data) - 1);
CHECK(!rec.poll[0].active);
CHECK(rec.abortN == 0);
finish();
}
static void testReadReadyRace(void)
{
start();
textOffer();
const uint8_t data[] = "ready while returning blocked";
proto.receiveData = data;
proto.receiveSize = sizeof(data) - 1;
rec.chunkResult = LG_CLIPBOARD_RESULT_BLOCKED;
rec.readyDuringChunk = true;
waylandCBRequest(203, LG_CLIPBOARD_DATA_TEXT);
CHECK(rec.pollN == 1);
pollFire(0, EPOLLIN);
CHECK(rec.streamData.beginN == 1);
CHECK(rec.streamData.chunkN == 2);
CHECK(rec.streamData.size == sizeof(data) - 1);
CHECK(rec.poll[0].events == EPOLLIN);
rec.endResult = LG_CLIPBOARD_RESULT_BLOCKED;
rec.readyDuringEnd = true;
pollFire(0, EPOLLIN);
CHECK(rec.streamData.endN == 2);
CHECK(rec.streamData.finalSize == sizeof(data) - 1);
CHECK(!rec.poll[0].active);
CHECK(rec.abortN == 0);
finish();
}
static void testReadError(void)
{
start();
@@ -666,13 +900,6 @@ static void testSource(void)
{
start();
waylandCBNotice(LG_CLIPBOARD_DATA_TEXT);
CHECK(rec.requestN == 1);
CHECK(rec.requestType == LG_CLIPBOARD_DATA_TEXT);
CHECK(rec.reply);
const uint8_t data[] = "source data";
rec.reply(rec.replyOpaque, LG_CLIPBOARD_DATA_TEXT,
data, sizeof(data) - 1);
CHECK(proto.sourceN == 1);
struct Proxy * source = &proto.source[0];
CHECK(proto.selection == source);
@@ -698,17 +925,45 @@ static void testSource(void)
(struct wl_data_source *)source, "text/plain", good[1]);
CHECK(rec.pollN == 1);
CHECK(rec.poll[0].fd == good[1]);
CHECK(rec.poll[0].events == 0);
CHECK(rec.requestN == 1);
CHECK(rec.requestType == LG_CLIPBOARD_DATA_TEXT);
CHECK(rec.requestStream);
CHECK(rec.requestOpaque);
const uint8_t first[] = "source ";
const uint8_t second[] = "data";
CHECK(rec.requestStream->begin(rec.requestOpaque,
LG_CLIPBOARD_DATA_TEXT, LG_CLIPBOARD_SIZE_UNKNOWN) ==
LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(rec.requestStream->chunk(rec.requestOpaque, 0,
first, sizeof(first) - 1) == LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(rec.poll[0].events == EPOLLOUT);
CHECK(rec.requestStream->chunk(rec.requestOpaque, sizeof(first) - 1,
second, sizeof(second) - 1) == LG_CLIPBOARD_RESULT_BLOCKED);
pollFire(0, EPOLLOUT);
CHECK(rec.requestReadyN == 1);
CHECK(rec.poll[0].events == 0);
CHECK(rec.requestStream->chunk(rec.requestOpaque, sizeof(first) - 1,
second, sizeof(second) - 1) == LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(rec.requestStream->end(rec.requestOpaque,
sizeof(first) + sizeof(second) - 2) == LG_CLIPBOARD_RESULT_BLOCKED);
pollFire(0, EPOLLOUT);
CHECK(rec.requestReadyN == 2);
CHECK(rec.requestStream->end(rec.requestOpaque,
sizeof(first) + sizeof(second) - 2) == LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(!rec.poll[0].active);
sourceListener(source)->cancelled(source->data,
(struct wl_data_source *)source);
CHECK(source->dead);
CHECK(proto.sourceDestroyN == 1);
pollFire(0, EPOLLOUT);
CHECK(rec.pollCleanupN == 1);
uint8_t actual[sizeof(data)] = {};
CHECK(read(good[0], actual, sizeof(actual)) == sizeof(data) - 1);
CHECK(memcmp(actual, data, sizeof(data) - 1) == 0);
uint8_t actual[sizeof(first) + sizeof(second)] = {};
const size_t expected = sizeof(first) + sizeof(second) - 2;
CHECK(read(good[0], actual, sizeof(actual)) == (ssize_t)expected);
CHECK(memcmp(actual, "source data", expected) == 0);
CHECK(read(good[0], &value, 1) == 0);
CHECK(close(good[0]) == 0);
checkClosed(good[1]);
@@ -716,6 +971,176 @@ static void testSource(void)
finish();
}
static void testSourceRelease(void)
{
start();
waylandCBNotice(LG_CLIPBOARD_DATA_TEXT);
struct Proxy * source = &proto.source[0];
CHECK(wlCb.selectionSource == (struct wl_data_source *)source);
CHECK(wlCb.sources);
CHECK(proto.selection == source);
CHECK(proto.selectionSetN == 1);
waylandCBRelease();
CHECK(!wlCb.selectionSource);
CHECK(wlCb.sources);
CHECK(!proto.selection);
CHECK(proto.selectionSetN == 2);
waylandCBRelease();
CHECK(proto.selectionSetN == 2);
sourceListener(source)->cancelled(source->data,
(struct wl_data_source *)source);
CHECK(source->dead);
CHECK(!wlCb.sources);
finish();
}
static void testSourceExternalOwner(void)
{
start();
waylandCBNotice(LG_CLIPBOARD_DATA_TEXT);
struct Proxy * source = &proto.source[0];
CHECK(proto.selectionSetN == 1);
struct Proxy * external = newOffer();
offerMime(external, "image/png");
selectOffer(external);
CHECK(!wlCb.selectionSource);
CHECK(proto.selection == external);
waylandCBRelease();
CHECK(proto.selectionSetN == 1);
CHECK(proto.selection == external);
sourceListener(source)->cancelled(source->data,
(struct wl_data_source *)source);
CHECK(source->dead);
finish();
}
static void testSourceReplacement(void)
{
start();
waylandCBNotice(LG_CLIPBOARD_DATA_TEXT);
struct Proxy * first = &proto.source[0];
waylandCBNotice(LG_CLIPBOARD_DATA_PNG);
struct Proxy * second = &proto.source[1];
CHECK(proto.selection == second);
CHECK(proto.selectionSetN == 2);
CHECK(wlCb.selectionSource == (struct wl_data_source *)second);
sourceListener(first)->cancelled(first->data,
(struct wl_data_source *)first);
CHECK(first->dead);
CHECK(wlCb.selectionSource == (struct wl_data_source *)second);
waylandCBRelease();
CHECK(!proto.selection);
CHECK(proto.selectionSetN == 3);
CHECK(!wlCb.selectionSource);
sourceListener(second)->cancelled(second->data,
(struct wl_data_source *)second);
CHECK(second->dead);
CHECK(!wlCb.sources);
finish();
}
static void testSourceCancelDuringRequest(void)
{
start();
waylandCBNotice(LG_CLIPBOARD_DATA_TEXT);
struct Proxy * source = &proto.source[0];
rec.requestCancelSync = true;
int fds[2];
CHECK(pipe(fds) == 0);
sourceListener(source)->send(source->data,
(struct wl_data_source *)source, "text/plain", fds[1]);
CHECK(rec.requestN == 1);
CHECK(rec.pollN == 1);
CHECK(!rec.poll[0].active);
CHECK(rec.pollCleanupN == 1);
checkClosed(fds[1]);
uint8_t value;
CHECK(read(fds[0], &value, 1) == 0);
CHECK(close(fds[0]) == 0);
sourceListener(source)->cancelled(source->data,
(struct wl_data_source *)source);
finish();
}
static void testSourceBeginDuringRequest(void)
{
start();
waylandCBNotice(LG_CLIPBOARD_DATA_TEXT);
struct Proxy * source = &proto.source[0];
rec.requestBeginSync = true;
int fds[2];
CHECK(pipe(fds) == 0);
sourceListener(source)->send(source->data,
(struct wl_data_source *)source, "text/plain", fds[1]);
CHECK(rec.requestN == 1);
CHECK(rec.requestBeginResult == LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(rec.requestOpaque);
CHECK(rec.requestReadyN == 1);
CHECK(rec.requestStream->end(rec.requestOpaque, 1) ==
LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(rec.pollCleanupN == 1);
uint8_t value = 0;
CHECK(read(fds[0], &value, 1) == 1);
CHECK(value == 0xa5);
CHECK(read(fds[0], &value, 1) == 0);
CHECK(close(fds[0]) == 0);
checkClosed(fds[1]);
sourceListener(source)->cancelled(source->data,
(struct wl_data_source *)source);
finish();
}
static void testSourceError(void)
{
start();
waylandCBNotice(LG_CLIPBOARD_DATA_TEXT);
struct Proxy * source = &proto.source[0];
int fds[2];
CHECK(pipe(fds) == 0);
sourceListener(source)->send(source->data,
(struct wl_data_source *)source, "text/plain", fds[1]);
CHECK(rec.requestStream->begin(rec.requestOpaque,
LG_CLIPBOARD_DATA_TEXT, LG_CLIPBOARD_SIZE_UNKNOWN) ==
LG_CLIPBOARD_RESULT_ACCEPTED);
const uint8_t first[] = "first";
const uint8_t second[] = "second";
CHECK(rec.requestStream->chunk(rec.requestOpaque, 0,
first, sizeof(first) - 1) == LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(rec.requestStream->chunk(rec.requestOpaque, sizeof(first) - 1,
second, sizeof(second) - 1) == LG_CLIPBOARD_RESULT_BLOCKED);
CHECK(close(fds[0]) == 0);
pollFire(0, EPOLLERR);
CHECK(rec.requestReadyN == 1);
CHECK(!rec.poll[0].active);
CHECK(rec.pollCleanupN == 1);
CHECK(rec.requestStream->chunk(rec.requestOpaque, sizeof(first) - 1,
second, sizeof(second) - 1) == LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(rec.requestStream->end(rec.requestOpaque,
sizeof(first) + sizeof(second) - 2) == LG_CLIPBOARD_RESULT_ACCEPTED);
checkClosed(fds[1]);
sourceListener(source)->cancelled(source->data,
(struct wl_data_source *)source);
finish();
}
static void testTeardown(void)
{
start();
@@ -725,17 +1150,40 @@ static void testTeardown(void)
const int fd = rec.poll[0].fd;
struct Proxy * dnd = rawOffer();
wlCb.dndOffer = (struct wl_data_offer *)dnd;
waylandCBNotice(LG_CLIPBOARD_DATA_TEXT);
struct Proxy * source = &proto.source[0];
finish();
CHECK(offer->dead);
CHECK(dnd->dead);
CHECK(source->dead);
CHECK(proto.offerDestroyN == 2);
CHECK(proto.sourceDestroyN == 1);
CHECK(proto.dirtyDestroyN == 0);
CHECK(proto.selectionSetN == 2);
CHECK(!proto.selection);
CHECK(rec.pollUnregisterN == 1);
CHECK(rec.pollCleanupN == 1);
checkClosed(fd);
}
static void testTeardownExternalOwner(void)
{
start();
waylandCBNotice(LG_CLIPBOARD_DATA_TEXT);
struct Proxy * source = &proto.source[0];
struct Proxy * external = newOffer();
offerMime(external, "text/plain");
selectOffer(external);
CHECK(!wlCb.selectionSource);
finish();
CHECK(source->dead);
CHECK(external->dead);
CHECK(proto.selectionSetN == 1);
CHECK(proto.selection == external);
}
static void testDnd(void)
{
start();
@@ -783,9 +1231,19 @@ static const struct Test tests[] =
{ "mime" , testMime },
{ "replace" , testReplace },
{ "read-eof" , testReadEOF },
{ "read-block", testReadBlocked },
{ "read-empty", testReadBlockedEmpty },
{ "read-ready", testReadReadyRace },
{ "read-error", testReadError },
{ "source" , testSource },
{ "source-release", testSourceRelease },
{ "source-external", testSourceExternalOwner },
{ "source-replace", testSourceReplacement },
{ "source-cancel", testSourceCancelDuringRequest },
{ "source-early", testSourceBeginDuringRequest },
{ "source-error", testSourceError },
{ "teardown" , testTeardown },
{ "teardown-external", testTeardownExternalOwner },
{ "self-copy" , testSelfCopy },
{ "dnd" , testDnd },
};

View File

@@ -22,6 +22,7 @@
#include "clipboard.h"
#include "test.h"
#include "x11.h"
#include "../src/clipboard.h"
#include "common/debug.h"
@@ -29,6 +30,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <X11/Xatom.h>
#include <X11/Xlib.h>
@@ -42,7 +44,7 @@
#define MAX_NOTICE 8U
#define MAX_PROP 16U
#define MAX_SEND 8U
#define MAX_TRANSFER 8192U
#define MAX_TRANSFER (128U * 1024U)
#define MAX_WINDOW 16U
#define A_CLIPBOARD 10UL
@@ -95,7 +97,7 @@ struct Property
struct TypeNotice
{
LG_ClipboardData type[LG_CLIPBOARD_DATA_NONE];
int count;
size_t count;
};
struct DataNotice
@@ -127,6 +129,7 @@ struct Log
unsigned int abortN;
unsigned int releaseN;
unsigned int requestN;
unsigned int requestReadyN;
unsigned int destroyN;
unsigned int freeN;
unsigned int flushN;
@@ -134,11 +137,27 @@ struct Log
unsigned int ungrabN;
unsigned int selectN;
LG_ClipboardData requestType;
LG_ClipboardReplyFn reply;
void * replyOpaque;
const LG_ClipboardStreamOps * requestStream;
void * requestOpaque;
LG_ClipboardRequest requestId;
LG_ClipboardRequest streamRequest;
LG_ClipboardData streamType;
uint64_t streamSizeHint;
unsigned int streamBeginN;
unsigned int streamChunkN;
unsigned int streamEndN;
size_t streamMaxChunk;
LG_ClipboardResult beginResult;
LG_ClipboardResult chunkResult;
LG_ClipboardResult endResult;
LG_ClipboardResult requestBeginResult;
bool readyDuringChunk;
bool readyDuringEnd;
Window owner;
Window nextWindow;
bool requestOK;
bool requestCancelSync;
bool requestBeginSync;
bool fixesOK;
};
@@ -147,6 +166,12 @@ struct X11DSAtoms x11atoms;
static struct Log rec;
static pthread_mutex_t readRaceLock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t readRaceCond = PTHREAD_COND_INITIALIZER;
static bool readRacePause;
static bool readRaceEntered;
static bool readRaceResume;
static size_t itemSize(int format, unsigned long count)
{
switch (format)
@@ -253,10 +278,7 @@ int XSelectInput(Display * display, Window window, long mask)
{
CHECK(display == x11.display);
CHECK(mask == PropertyChangeMask);
bool found = false;
for (unsigned int i = 0; i < rec.windowN; ++i)
found |= rec.window[i].window == window && !rec.window[i].dead;
CHECK(found);
CHECK(window != None);
return Success;
}
@@ -339,26 +361,44 @@ int XGetWindowProperty(Display * display, Window window, Atom property,
CHECK(display == x11.display);
CHECK(window != None);
CHECK(property != None);
CHECK(offset == 0);
CHECK(length == ~0L);
CHECK(offset >= 0);
CHECK(length == ~0L || length == (64U * 1024U + 3U) / 4U);
CHECK(delete);
CHECK(requestedType == AnyPropertyType);
CHECK(rec.propPos < rec.propN);
const struct Property * prop = &rec.prop[rec.propPos++];
const struct Property * prop = &rec.prop[rec.propPos];
*actualType = prop->type;
*actualFormat = prop->format;
*count = prop->count;
*after = prop->after;
size_t start = 0;
size_t size = prop->size;
if (prop->format == 8 && length != ~0L)
{
start = (size_t)offset * 4U;
CHECK(start <= prop->size);
const size_t maximum = (size_t)length * 4U;
size = prop->size - start;
if (size > maximum)
size = maximum;
*count = size;
*after = prop->size - start - size;
}
else
{
*count = prop->count;
*after = prop->after;
}
if (prop->null)
*data = NULL;
else
{
CHECK(prop->size <= sizeof(prop->data));
*data = malloc(prop->size ? prop->size : 1);
CHECK(start + size <= sizeof(prop->data));
*data = malloc(size ? size : 1);
CHECK(*data);
if (prop->size)
memcpy(*data, prop->data, prop->size);
if (size)
memcpy(*data, prop->data + start, size);
}
if (*after == 0)
++rec.propPos;
return prop->status;
}
@@ -370,17 +410,17 @@ int XFree(void * data)
return Success;
}
void app_clipboardNotifyTypes(const LG_ClipboardData types[], int count)
void lgClipboard_notifyTypes(
const LG_ClipboardData types[], size_t count)
{
CHECK(rec.noticeN < ARRAY_LENGTH(rec.notice));
CHECK(count >= 0);
CHECK(count <= LG_CLIPBOARD_DATA_NONE);
CHECK(count <= ARRAY_LENGTH(rec.notice[0].type));
struct TypeNotice * notice = &rec.notice[rec.noticeN++];
notice->count = count;
memcpy(notice->type, types, (size_t)count * sizeof(*types));
memcpy(notice->type, types, count * sizeof(*types));
}
void app_clipboardData(LG_ClipboardRequest request,
void lgClipboard_data(LG_ClipboardRequest request,
LG_ClipboardData type, const void * data, size_t size)
{
CHECK(rec.dataN < ARRAY_LENGTH(rec.data));
@@ -394,27 +434,121 @@ void app_clipboardData(LG_ClipboardRequest request,
memcpy(notice->data, data, size);
}
void app_clipboardAbort(LG_ClipboardRequest request)
LG_ClipboardResult lgClipboard_dataBegin(LG_ClipboardRequest request,
LG_ClipboardData type, uint64_t sizeHint)
{
++rec.streamBeginN;
rec.streamRequest = request;
rec.streamType = type;
rec.streamSizeHint = sizeHint;
if (rec.beginResult == LG_CLIPBOARD_RESULT_ACCEPTED)
{
CHECK(rec.dataN < ARRAY_LENGTH(rec.data));
rec.data[rec.dataN] = (struct DataNotice)
{
.request = request,
.type = type,
};
}
return rec.beginResult;
}
LG_ClipboardResult lgClipboard_dataChunk(LG_ClipboardRequest request,
uint64_t offset, const void * data, size_t size)
{
const LG_ClipboardResult result = rec.chunkResult;
++rec.streamChunkN;
CHECK(request == rec.streamRequest);
CHECK(rec.dataN < ARRAY_LENGTH(rec.data));
struct DataNotice * notice = &rec.data[rec.dataN];
CHECK(offset == notice->size);
CHECK(data);
CHECK(size);
CHECK(size <= sizeof(notice->data) - notice->size);
if (size > rec.streamMaxChunk)
rec.streamMaxChunk = size;
pthread_mutex_lock(&readRaceLock);
if (readRacePause)
{
readRacePause = false;
readRaceEntered = true;
pthread_cond_signal(&readRaceCond);
while (!readRaceResume)
pthread_cond_wait(&readRaceCond, &readRaceLock);
}
pthread_mutex_unlock(&readRaceLock);
if (rec.readyDuringChunk)
{
rec.readyDuringChunk = false;
rec.chunkResult = LG_CLIPBOARD_RESULT_ACCEPTED;
x11CBRequestReady(request);
}
if (result == LG_CLIPBOARD_RESULT_ACCEPTED)
{
memcpy(notice->data + notice->size, data, size);
notice->size += size;
}
return result;
}
LG_ClipboardResult lgClipboard_dataEnd(LG_ClipboardRequest request,
uint64_t finalSize)
{
const LG_ClipboardResult result = rec.endResult;
++rec.streamEndN;
CHECK(request == rec.streamRequest);
CHECK(rec.dataN < ARRAY_LENGTH(rec.data));
if (rec.readyDuringEnd)
{
rec.readyDuringEnd = false;
rec.endResult = LG_CLIPBOARD_RESULT_ACCEPTED;
x11CBRequestReady(request);
}
if (result == LG_CLIPBOARD_RESULT_ACCEPTED)
{
CHECK(finalSize == rec.data[rec.dataN].size);
++rec.dataN;
}
return result;
}
void lgClipboard_abort(LG_ClipboardRequest request)
{
CHECK(rec.abortN < ARRAY_LENGTH(rec.abort));
rec.abort[rec.abortN++] = request;
}
void app_clipboardRelease(void)
void lgClipboard_release(void)
{
++rec.releaseN;
}
bool app_clipboardRequest(LG_ClipboardData type,
LG_ClipboardReplyFn replyFn, void * opaque)
bool lgClipboard_requestStream(LG_ClipboardData type,
const LG_ClipboardStreamOps * stream, void * opaque,
LG_ClipboardRequest * request)
{
++rec.requestN;
rec.requestType = type;
rec.reply = replyFn;
rec.replyOpaque = opaque;
rec.requestStream = stream;
rec.requestOpaque = opaque;
rec.requestId = 9000 + rec.requestN;
if (request)
*request = rec.requestId;
if (rec.requestBeginSync)
rec.requestBeginResult = stream->begin(
opaque, type, LG_CLIPBOARD_SIZE_UNKNOWN);
if (rec.requestCancelSync)
stream->cancel(opaque, LG_CLIPBOARD_CANCEL_REPLACED);
return rec.requestOK;
}
bool lgClipboard_requestReady(LG_ClipboardRequest request)
{
CHECK(request == rec.requestId);
++rec.requestReadyN;
return true;
}
static struct Property * prop(void)
{
CHECK(rec.propN < ARRAY_LENGTH(rec.prop));
@@ -469,17 +603,22 @@ static void selection(Window requestor, Atom target, Atom property)
CHECK(x11CBEventThread(&event));
}
static void property(Window window)
static void propertyState(Window window, Atom atom, int state)
{
XEvent event = {};
event.xproperty.type = PropertyNotify;
event.xproperty.display = x11.display;
event.xproperty.window = window;
event.xproperty.atom = x11atoms.SEL_DATA;
event.xproperty.state = PropertyNewValue;
event.xproperty.atom = atom;
event.xproperty.state = state;
CHECK(x11CBEventThread(&event));
}
static void property(Window window)
{
propertyState(window, x11atoms.SEL_DATA, PropertyNewValue);
}
static void owner(Window owner)
{
rec.owner = owner;
@@ -540,6 +679,15 @@ static void start(void)
rec.nextWindow = 1000;
rec.requestOK = true;
rec.fixesOK = true;
rec.beginResult = LG_CLIPBOARD_RESULT_ACCEPTED;
rec.chunkResult = LG_CLIPBOARD_RESULT_ACCEPTED;
rec.endResult = LG_CLIPBOARD_RESULT_ACCEPTED;
pthread_mutex_lock(&readRaceLock);
readRacePause = false;
readRaceEntered = false;
readRaceResume = false;
pthread_mutex_unlock(&readRaceLock);
x11.display = (Display *)(uintptr_t)1;
x11.window = 50;
@@ -632,6 +780,166 @@ static void testIncr(void)
finish();
}
static void testIncrBlocked(void)
{
start();
const unsigned long targets[] = { A_TEXT };
discover(620, targets, ARRAY_LENGTH(targets));
const Window window = request(211, LG_CLIPBOARD_DATA_TEXT);
const unsigned long capacity = 9;
rec.beginResult = LG_CLIPBOARD_RESULT_BLOCKED;
prop32(x11atoms.INCR, &capacity, 1);
selection(window, A_TEXT, x11atoms.SEL_DATA);
CHECK(rec.streamBeginN == 1);
CHECK(rec.dataN == 0);
const uint8_t first[] = "abc";
prop8(A_TEXT, first, sizeof(first) - 1);
property(window);
CHECK(rec.streamChunkN == 0);
rec.beginResult = LG_CLIPBOARD_RESULT_ACCEPTED;
rec.chunkResult = LG_CLIPBOARD_RESULT_BLOCKED;
x11CBRequestReady(211);
CHECK(rec.streamBeginN == 2);
CHECK(rec.streamSizeHint == capacity);
CHECK(rec.streamChunkN == 1);
CHECK(rec.data[0].size == 0);
const uint8_t second[] = "defghi";
prop8(A_TEXT, second, sizeof(second) - 1);
property(window);
CHECK(rec.streamChunkN == 1);
rec.chunkResult = LG_CLIPBOARD_RESULT_ACCEPTED;
x11CBRequestReady(211);
CHECK(rec.streamChunkN == 3);
CHECK(rec.data[0].size == 9);
CHECK(memcmp(rec.data[0].data, "abcdefghi", 9) == 0);
rec.endResult = LG_CLIPBOARD_RESULT_BLOCKED;
propNull(A_TEXT, 8, 0);
property(window);
CHECK(rec.streamEndN == 1);
CHECK(rec.dataN == 0);
rec.endResult = LG_CLIPBOARD_RESULT_ACCEPTED;
x11CBRequestReady(211);
CHECK(rec.streamEndN == 2);
CHECK(rec.dataN == 1);
CHECK(rec.abortN == 0);
finish();
}
static void testIncrReadyRace(void)
{
start();
const unsigned long targets[] = { A_TEXT };
discover(622, targets, ARRAY_LENGTH(targets));
const Window window = request(213, LG_CLIPBOARD_DATA_TEXT);
const unsigned long capacity = 4;
prop32(x11atoms.INCR, &capacity, 1);
selection(window, A_TEXT, x11atoms.SEL_DATA);
const uint8_t data[] = "race";
rec.chunkResult = LG_CLIPBOARD_RESULT_BLOCKED;
rec.readyDuringChunk = true;
prop8(A_TEXT, data, sizeof(data) - 1);
property(window);
CHECK(rec.streamChunkN == 2);
CHECK(rec.data[0].size == sizeof(data) - 1);
rec.endResult = LG_CLIPBOARD_RESULT_BLOCKED;
rec.readyDuringEnd = true;
propNull(A_TEXT, 8, 0);
property(window);
CHECK(rec.streamEndN == 2);
CHECK(rec.dataN == 1);
CHECK(rec.abortN == 0);
finish();
}
static void * requestReadyThread(void * opaque)
{
x11CBRequestReady(*(const LG_ClipboardRequest *)opaque);
return NULL;
}
static void testIncrCancelRace(void)
{
start();
const unsigned long targets[] = { A_TEXT };
discover(623, targets, ARRAY_LENGTH(targets));
const LG_ClipboardRequest id = 214;
const Window window = request(id, LG_CLIPBOARD_DATA_TEXT);
const unsigned long capacity = 6;
prop32(x11atoms.INCR, &capacity, 1);
selection(window, A_TEXT, x11atoms.SEL_DATA);
const uint8_t data[] = "cancel";
rec.chunkResult = LG_CLIPBOARD_RESULT_BLOCKED;
prop8(A_TEXT, data, sizeof(data) - 1);
property(window);
CHECK(rec.streamChunkN == 1);
rec.chunkResult = LG_CLIPBOARD_RESULT_ACCEPTED;
pthread_mutex_lock(&readRaceLock);
readRacePause = true;
pthread_mutex_unlock(&readRaceLock);
pthread_t readyThread;
CHECK(pthread_create(&readyThread, NULL, requestReadyThread,
(void *)&id) == 0);
pthread_mutex_lock(&readRaceLock);
while (!readRaceEntered)
pthread_cond_wait(&readRaceCond, &readRaceLock);
pthread_mutex_unlock(&readRaceLock);
/* The callback is outside x11cb.lock and still consuming its chunk. */
x11CBRequestCancel(id, LG_CLIPBOARD_CANCEL_REPLACED);
pthread_mutex_lock(&readRaceLock);
readRaceResume = true;
pthread_cond_signal(&readRaceCond);
pthread_mutex_unlock(&readRaceLock);
CHECK(pthread_join(readyThread, NULL) == 0);
CHECK(rec.streamChunkN == 2);
CHECK(rec.data[0].size == sizeof(data) - 1);
CHECK(memcmp(rec.data[0].data, data, sizeof(data) - 1) == 0);
CHECK(rec.abortN == 0);
finish();
}
static void testIncrLargeProperty(void)
{
start();
const unsigned long targets[] = { A_TEXT };
discover(621, targets, ARRAY_LENGTH(targets));
const Window window = request(212, LG_CLIPBOARD_DATA_TEXT);
const unsigned long capacity = 70U * 1024U;
prop32(x11atoms.INCR, &capacity, 1);
selection(window, A_TEXT, x11atoms.SEL_DATA);
const size_t size = 70U * 1024U;
uint8_t * data = malloc(size);
CHECK(data);
for (size_t i = 0; i < size; ++i)
data[i] = (uint8_t)i;
prop8(A_TEXT, data, size);
property(window);
propNull(A_TEXT, 8, 0);
property(window);
CHECK(rec.streamChunkN == 2);
CHECK(rec.streamMaxChunk == 64U * 1024U);
CHECK(rec.dataN == 1);
CHECK(rec.data[0].size == size);
CHECK(memcmp(rec.data[0].data, data, size) == 0);
free(data);
finish();
}
static void testMalformed(void)
{
start();
@@ -717,36 +1025,88 @@ static void testSource(void)
selectionRequest(A_PNG, 701);
CHECK(rec.requestN == 1);
CHECK(rec.requestType == LG_CLIPBOARD_DATA_PNG);
CHECK(rec.reply);
CHECK(rec.requestStream);
CHECK(rec.sendN == 1);
const uint8_t data[] = { 1, 2, 3, 4 };
rec.reply(rec.replyOpaque, LG_CLIPBOARD_DATA_PNG,
data, ARRAY_LENGTH(data));
CHECK(rec.requestStream->begin(rec.requestOpaque,
LG_CLIPBOARD_DATA_PNG, LG_CLIPBOARD_SIZE_UNKNOWN) ==
LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(rec.changeN == 2);
CHECK(rec.change[1].property == 701);
CHECK(rec.change[1].type == A_PNG);
CHECK(rec.change[1].format == 8);
CHECK(rec.change[1].count == 4);
CHECK(memcmp(rec.change[1].data, data, sizeof(data)) == 0);
CHECK(rec.change[1].type == x11atoms.INCR);
CHECK(rec.change[1].format == 32);
CHECK(rec.change[1].count == 1);
CHECK(rec.sendN == 2);
CHECK(rec.send[1].xselection.property == 701);
selectionRequest(A_TEXT, 702);
CHECK(rec.requestN == 1);
CHECK(rec.requestStream->chunk(rec.requestOpaque, 0,
data, ARRAY_LENGTH(data)) == LG_CLIPBOARD_RESULT_BLOCKED);
propertyState(900, 701, PropertyDelete);
CHECK(rec.requestReadyN == 1);
CHECK(rec.requestStream->chunk(rec.requestOpaque, 0,
data, ARRAY_LENGTH(data)) == LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(rec.changeN == 3);
CHECK(rec.change[2].property == 701);
CHECK(rec.change[2].type == A_PNG);
CHECK(rec.change[2].format == 8);
CHECK(rec.change[2].count == 4);
CHECK(memcmp(rec.change[2].data, data, sizeof(data)) == 0);
CHECK(rec.requestStream->end(rec.requestOpaque, ARRAY_LENGTH(data)) ==
LG_CLIPBOARD_RESULT_BLOCKED);
propertyState(900, 701, PropertyDelete);
CHECK(rec.requestReadyN == 2);
CHECK(rec.requestStream->end(rec.requestOpaque, ARRAY_LENGTH(data)) ==
LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(rec.changeN == 4);
CHECK(rec.change[3].property == 701);
CHECK(rec.change[3].type == A_PNG);
CHECK(rec.change[3].format == 8);
CHECK(rec.change[3].count == 0);
rec.requestCancelSync = true;
selectionRequest(A_PNG, 704);
CHECK(rec.requestN == 2);
CHECK(rec.sendN == 3);
CHECK(rec.send[2].xselection.property == None);
rec.requestCancelSync = false;
rec.requestOK = false;
selectionRequest(A_PNG, 703);
selectionRequest(A_TEXT, 702);
CHECK(rec.requestN == 2);
CHECK(rec.sendN == 4);
CHECK(rec.send[3].xselection.property == None);
rec.requestOK = false;
selectionRequest(A_PNG, 703);
CHECK(rec.requestN == 3);
CHECK(rec.sendN == 5);
CHECK(rec.send[4].xselection.property == None);
x11CBRelease();
CHECK(rec.owner == None);
finish();
}
static void testSourceBeginDuringRequest(void)
{
start();
x11CBNotice(LG_CLIPBOARD_DATA_PNG);
rec.requestBeginSync = true;
selectionRequest(A_PNG, 705);
CHECK(rec.requestN == 1);
CHECK(rec.requestBeginResult == LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(rec.requestOpaque);
CHECK(rec.changeN == 1);
CHECK(rec.change[0].type == x11atoms.INCR);
CHECK(rec.sendN == 1);
propertyState(900, 705, PropertyDelete);
CHECK(rec.requestStream->end(rec.requestOpaque, 0) ==
LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(rec.changeN == 2);
CHECK(rec.change[1].count == 0);
finish();
}
static void testTeardown(void)
{
start();
@@ -789,9 +1149,14 @@ static const struct Test tests[] =
{ "targets" , testTargets },
{ "normal" , testNormal },
{ "incr" , testIncr },
{ "incr-block", testIncrBlocked },
{ "incr-ready", testIncrReadyRace },
{ "incr-cancel", testIncrCancelRace },
{ "incr-large", testIncrLargeProperty },
{ "malformed", testMalformed },
{ "replace" , testReplace },
{ "source" , testSource },
{ "source-early", testSourceBeginDuringRequest },
{ "teardown" , testTeardown },
};