mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 15:11:31 +00:00
[client] clipboard: add direct LGMP synchronization
This commit is contained in:
@@ -29,8 +29,9 @@
|
||||
#include <unistd.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "app.h"
|
||||
#include "../../src/clipboard.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/KVMFRClipboard.h"
|
||||
|
||||
struct DataOffer {
|
||||
bool isSelfCopy;
|
||||
@@ -221,12 +222,18 @@ static bool clipboardReadClaim(struct ClipboardRead * data)
|
||||
return current;
|
||||
}
|
||||
|
||||
static void clipboardReadRelease(struct ClipboardRead * data)
|
||||
{
|
||||
if (atomic_fetch_sub_explicit(
|
||||
&data->references, 1, memory_order_acq_rel) == 1)
|
||||
free(data);
|
||||
}
|
||||
|
||||
static void clipboardReadCleanup(void * opaque)
|
||||
{
|
||||
struct ClipboardRead * data = opaque;
|
||||
close(data->fd);
|
||||
free(data->buf);
|
||||
free(data);
|
||||
clipboardReadRelease(data);
|
||||
}
|
||||
|
||||
// Destination client handlers.
|
||||
@@ -302,6 +309,9 @@ static void dataDeviceHandleSelection(void * opaque,
|
||||
{
|
||||
if (!offer)
|
||||
{
|
||||
LG_LOCK(wlCb.lock);
|
||||
wlCb.selectionSource = NULL;
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
waylandCBInvalidate();
|
||||
return;
|
||||
}
|
||||
@@ -309,6 +319,12 @@ static void dataDeviceHandleSelection(void * opaque,
|
||||
struct DataOffer * extra = wl_data_offer_get_user_data(offer);
|
||||
if (!hasAnyMimetype(extra->mimetypes) || extra->isSelfCopy)
|
||||
{
|
||||
if (!extra->isSelfCopy)
|
||||
{
|
||||
LG_LOCK(wlCb.lock);
|
||||
wlCb.selectionSource = NULL;
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
}
|
||||
wl_data_offer_set_user_data(offer, NULL);
|
||||
for (enum LG_ClipboardData i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
free(extra->mimetypes[i]);
|
||||
@@ -318,7 +334,7 @@ static void dataDeviceHandleSelection(void * opaque,
|
||||
return;
|
||||
}
|
||||
|
||||
int idx = 0;
|
||||
size_t idx = 0;
|
||||
enum LG_ClipboardData types[LG_CLIPBOARD_DATA_NONE];
|
||||
for (enum LG_ClipboardData i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
if (extra->mimetypes[i])
|
||||
@@ -329,6 +345,7 @@ static void dataDeviceHandleSelection(void * opaque,
|
||||
struct ClipboardRead * read = clipboardReadTakeCurrentNL();
|
||||
struct wl_data_offer * oldOffer = wlCb.offer;
|
||||
memcpy(oldMimetypes, wlCb.mimetypes, sizeof(oldMimetypes));
|
||||
wlCb.selectionSource = NULL;
|
||||
wlCb.offer = offer;
|
||||
memcpy(wlCb.mimetypes, extra->mimetypes, sizeof(wlCb.mimetypes));
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
@@ -341,14 +358,14 @@ static void dataDeviceHandleSelection(void * opaque,
|
||||
{
|
||||
const LG_ClipboardRequest request = read->request;
|
||||
clipboardReadRetire(read);
|
||||
app_clipboardAbort(request);
|
||||
lgClipboard_abort(request);
|
||||
}
|
||||
if (oldOffer && oldOffer != offer)
|
||||
wl_data_offer_destroy(oldOffer);
|
||||
for (enum LG_ClipboardData i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
free(oldMimetypes[i]);
|
||||
|
||||
app_clipboardNotifyTypes(types, idx);
|
||||
lgClipboard_notifyTypes(types, idx);
|
||||
}
|
||||
|
||||
static void dataDeviceHandleEnter(void * data, struct wl_data_device * device,
|
||||
@@ -425,54 +442,208 @@ bool waylandCBInit(void)
|
||||
return true;
|
||||
}
|
||||
|
||||
enum ClipboardReadOperation
|
||||
{
|
||||
CLIPBOARD_READ_BEGIN,
|
||||
CLIPBOARD_READ_CHUNK,
|
||||
CLIPBOARD_READ_END,
|
||||
};
|
||||
|
||||
enum ClipboardReadCallResult
|
||||
{
|
||||
CLIPBOARD_READ_CALL_STALE,
|
||||
CLIPBOARD_READ_CALL_ACCEPTED,
|
||||
CLIPBOARD_READ_CALL_BLOCKED,
|
||||
CLIPBOARD_READ_CALL_RETRY,
|
||||
CLIPBOARD_READ_CALL_FAILED,
|
||||
};
|
||||
|
||||
/* The provider's ready callback can run after its operation has returned but
|
||||
* before this thread records BLOCKED. Keep the call in progress until the
|
||||
* result is committed under wlCb.lock so that edge is retained. */
|
||||
static enum ClipboardReadCallResult clipboardReadCall(
|
||||
struct ClipboardRead * data, enum ClipboardReadOperation operation)
|
||||
{
|
||||
LG_ClipboardRequest request;
|
||||
LG_ClipboardData type;
|
||||
uint64_t offset;
|
||||
size_t pending;
|
||||
|
||||
LG_LOCK(wlCb.lock);
|
||||
if (wlCb.currentRead != data || data->blocked || data->calling)
|
||||
{
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
return CLIPBOARD_READ_CALL_STALE;
|
||||
}
|
||||
|
||||
data->calling = true;
|
||||
data->readyPending = false;
|
||||
request = data->request;
|
||||
type = data->type;
|
||||
offset = data->offset;
|
||||
pending = data->pending;
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
|
||||
LG_ClipboardResult result;
|
||||
switch (operation)
|
||||
{
|
||||
case CLIPBOARD_READ_BEGIN:
|
||||
result = lgClipboard_dataBegin(
|
||||
request, type, LG_CLIPBOARD_SIZE_UNKNOWN);
|
||||
break;
|
||||
|
||||
case CLIPBOARD_READ_CHUNK:
|
||||
result = lgClipboard_dataChunk(
|
||||
request, offset, data->buffer, pending);
|
||||
break;
|
||||
|
||||
case CLIPBOARD_READ_END:
|
||||
result = lgClipboard_dataEnd(request, offset);
|
||||
break;
|
||||
|
||||
default:
|
||||
result = LG_CLIPBOARD_RESULT_FAILED;
|
||||
break;
|
||||
}
|
||||
|
||||
LG_LOCK(wlCb.lock);
|
||||
if (wlCb.currentRead != data)
|
||||
{
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
return CLIPBOARD_READ_CALL_STALE;
|
||||
}
|
||||
|
||||
data->calling = false;
|
||||
if (result == LG_CLIPBOARD_RESULT_BLOCKED)
|
||||
{
|
||||
if (data->readyPending)
|
||||
{
|
||||
data->readyPending = false;
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
return CLIPBOARD_READ_CALL_RETRY;
|
||||
}
|
||||
|
||||
data->blocked = true;
|
||||
waylandPollUpdate(data->fd, 0);
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
return CLIPBOARD_READ_CALL_BLOCKED;
|
||||
}
|
||||
|
||||
data->readyPending = false;
|
||||
if (result != LG_CLIPBOARD_RESULT_ACCEPTED)
|
||||
{
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
return CLIPBOARD_READ_CALL_FAILED;
|
||||
}
|
||||
|
||||
switch (operation)
|
||||
{
|
||||
case CLIPBOARD_READ_BEGIN:
|
||||
data->begun = true;
|
||||
break;
|
||||
|
||||
case CLIPBOARD_READ_CHUNK:
|
||||
data->offset += data->pending;
|
||||
data->pending = 0;
|
||||
break;
|
||||
|
||||
case CLIPBOARD_READ_END:
|
||||
break;
|
||||
}
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
return CLIPBOARD_READ_CALL_ACCEPTED;
|
||||
}
|
||||
|
||||
static void clipboardReadCallback(uint32_t events, void * opaque)
|
||||
{
|
||||
struct ClipboardRead * data = opaque;
|
||||
LG_LOCK(wlCb.lock);
|
||||
const bool active = wlCb.currentRead == data;
|
||||
const bool busy = active && (data->blocked || data->calling);
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
if (!active)
|
||||
if (!active || busy)
|
||||
return;
|
||||
|
||||
if (events & EPOLLERR)
|
||||
{
|
||||
if (clipboardReadClaim(data))
|
||||
app_clipboardAbort(data->request);
|
||||
lgClipboard_abort(data->request);
|
||||
return;
|
||||
}
|
||||
|
||||
ssize_t result = read(data->fd,
|
||||
data->buf + data->numRead, data->size - data->numRead);
|
||||
if (result < 0)
|
||||
bool readAttempted = false;
|
||||
for (;;)
|
||||
{
|
||||
DEBUG_ERROR("Failed to read from clipboard: %s", strerror(errno));
|
||||
if (clipboardReadClaim(data))
|
||||
app_clipboardAbort(data->request);
|
||||
return;
|
||||
}
|
||||
enum ClipboardReadOperation operation;
|
||||
bool haveOperation = true;
|
||||
int readError = 0;
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
if (clipboardReadClaim(data))
|
||||
app_clipboardData(
|
||||
data->request, data->type, data->buf, data->numRead);
|
||||
return;
|
||||
}
|
||||
|
||||
data->numRead += result;
|
||||
if (data->numRead >= data->size)
|
||||
{
|
||||
data->size *= 2;
|
||||
void * nbuf = realloc(data->buf, data->size);
|
||||
if (!nbuf)
|
||||
LG_LOCK(wlCb.lock);
|
||||
if (wlCb.currentRead != data || data->blocked || data->calling)
|
||||
{
|
||||
DEBUG_ERROR("Failed to realloc clipboard buffer: %s", strerror(errno));
|
||||
if (clipboardReadClaim(data))
|
||||
app_clipboardAbort(data->request);
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
return;
|
||||
}
|
||||
|
||||
data->buf = nbuf;
|
||||
if (!data->begun)
|
||||
operation = CLIPBOARD_READ_BEGIN;
|
||||
else if (data->pending)
|
||||
operation = CLIPBOARD_READ_CHUNK;
|
||||
else if (data->eof)
|
||||
operation = CLIPBOARD_READ_END;
|
||||
else if (!readAttempted)
|
||||
{
|
||||
const ssize_t result = read(
|
||||
data->fd, data->buffer, sizeof(data->buffer));
|
||||
readAttempted = true;
|
||||
if (result < 0)
|
||||
{
|
||||
readError = errno;
|
||||
haveOperation = false;
|
||||
}
|
||||
else if (result == 0)
|
||||
{
|
||||
data->eof = true;
|
||||
operation = CLIPBOARD_READ_END;
|
||||
}
|
||||
else
|
||||
{
|
||||
data->pending = (size_t)result;
|
||||
operation = CLIPBOARD_READ_CHUNK;
|
||||
}
|
||||
}
|
||||
else
|
||||
haveOperation = false;
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
|
||||
if (readError)
|
||||
{
|
||||
if (readError == EAGAIN || readError == EWOULDBLOCK)
|
||||
return;
|
||||
DEBUG_ERROR("Failed to read from clipboard: %s", strerror(readError));
|
||||
if (clipboardReadClaim(data))
|
||||
lgClipboard_abort(data->request);
|
||||
return;
|
||||
}
|
||||
if (!haveOperation)
|
||||
return;
|
||||
|
||||
const enum ClipboardReadCallResult result =
|
||||
clipboardReadCall(data, operation);
|
||||
if (result == CLIPBOARD_READ_CALL_RETRY ||
|
||||
result == CLIPBOARD_READ_CALL_ACCEPTED)
|
||||
{
|
||||
if (operation == CLIPBOARD_READ_END &&
|
||||
result == CLIPBOARD_READ_CALL_ACCEPTED)
|
||||
{
|
||||
clipboardReadClaim(data);
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (result == CLIPBOARD_READ_CALL_FAILED)
|
||||
clipboardReadClaim(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -491,10 +662,10 @@ void waylandCBInvalidate(void)
|
||||
{
|
||||
const LG_ClipboardRequest request = read->request;
|
||||
clipboardReadRetire(read);
|
||||
app_clipboardAbort(request);
|
||||
lgClipboard_abort(request);
|
||||
}
|
||||
|
||||
app_clipboardRelease();
|
||||
lgClipboard_release();
|
||||
|
||||
if (offer)
|
||||
wl_data_offer_destroy(offer);
|
||||
@@ -512,7 +683,21 @@ void waylandCBRequest(LG_ClipboardRequest request, LG_ClipboardData type)
|
||||
if (pipe(fds) < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to get a clipboard pipe: %s", strerror(errno));
|
||||
app_clipboardAbort(request);
|
||||
lgClipboard_abort(request);
|
||||
return;
|
||||
}
|
||||
const int readFlags = fcntl(fds[0], F_GETFL);
|
||||
const int readFdFlags = fcntl(fds[0], F_GETFD);
|
||||
const int writeFdFlags = fcntl(fds[1], F_GETFD);
|
||||
if (readFlags < 0 || readFdFlags < 0 || writeFdFlags < 0 ||
|
||||
fcntl(fds[0], F_SETFD, readFdFlags | FD_CLOEXEC) < 0 ||
|
||||
fcntl(fds[1], F_SETFD, writeFdFlags | FD_CLOEXEC) < 0 ||
|
||||
fcntl(fds[0], F_SETFL, readFlags | O_NONBLOCK) < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to configure clipboard pipe: %s", strerror(errno));
|
||||
close(fds[0]);
|
||||
close(fds[1]);
|
||||
lgClipboard_abort(request);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -522,26 +707,17 @@ void waylandCBRequest(LG_ClipboardRequest request, LG_ClipboardData type)
|
||||
DEBUG_ERROR("Failed to allocate memory to read clipboard");
|
||||
close(fds[0]);
|
||||
close(fds[1]);
|
||||
app_clipboardAbort(request);
|
||||
lgClipboard_abort(request);
|
||||
return;
|
||||
}
|
||||
|
||||
data->fd = fds[0];
|
||||
data->size = 4096;
|
||||
data->numRead = 0;
|
||||
data->buf = malloc(data->size);
|
||||
data->request = request;
|
||||
data->type = type;
|
||||
|
||||
if (!data->buf)
|
||||
*data = (struct ClipboardRead)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate memory to receive clipboard data");
|
||||
close(data->fd);
|
||||
close(fds[1]);
|
||||
free(data);
|
||||
app_clipboardAbort(request);
|
||||
return;
|
||||
}
|
||||
.fd = fds[0],
|
||||
.request = request,
|
||||
.type = type,
|
||||
};
|
||||
atomic_init(&data->references, 1);
|
||||
|
||||
LG_LOCK(wlCb.lock);
|
||||
struct ClipboardRead * old = clipboardReadTakeCurrentNL();
|
||||
@@ -564,7 +740,7 @@ void waylandCBRequest(LG_ClipboardRequest request, LG_ClipboardData type)
|
||||
{
|
||||
const LG_ClipboardRequest oldRequest = old->request;
|
||||
clipboardReadRetire(old);
|
||||
app_clipboardAbort(oldRequest);
|
||||
lgClipboard_abort(oldRequest);
|
||||
}
|
||||
if (!registered)
|
||||
{
|
||||
@@ -572,46 +748,302 @@ void waylandCBRequest(LG_ClipboardRequest request, LG_ClipboardData type)
|
||||
DEBUG_ERROR("Failed to register clipboard read into epoll: %s",
|
||||
strerror(errno));
|
||||
clipboardReadCleanup(data);
|
||||
app_clipboardAbort(request);
|
||||
lgClipboard_abort(request);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void waylandCBRequestReady(LG_ClipboardRequest request)
|
||||
{
|
||||
struct ClipboardRead * failed = NULL;
|
||||
struct ClipboardRead * retry = NULL;
|
||||
LG_LOCK(wlCb.lock);
|
||||
struct ClipboardRead * data = wlCb.currentRead;
|
||||
if (data && data->request == request && data->calling)
|
||||
data->readyPending = true;
|
||||
else if (data && data->request == request && data->blocked)
|
||||
{
|
||||
data->blocked = false;
|
||||
if (!waylandPollUpdate(data->fd, EPOLLIN))
|
||||
failed = clipboardReadTakeCurrentNL();
|
||||
else
|
||||
{
|
||||
atomic_fetch_add_explicit(
|
||||
&data->references, 1, memory_order_relaxed);
|
||||
retry = data;
|
||||
}
|
||||
}
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
|
||||
if (retry)
|
||||
{
|
||||
/* A blocked operation may already own bytes in data->buffer. Retry it
|
||||
* directly: an empty pipe with an open writer has no EPOLLIN state with
|
||||
* which to wake the pending operation. The extra reference covers
|
||||
* cancellation while this non-poll dispatch is in progress. */
|
||||
clipboardReadCallback(0, retry);
|
||||
clipboardReadRelease(retry);
|
||||
}
|
||||
|
||||
if (failed)
|
||||
{
|
||||
const LG_ClipboardRequest id = failed->request;
|
||||
clipboardReadRetire(failed);
|
||||
lgClipboard_abort(id);
|
||||
}
|
||||
}
|
||||
|
||||
void waylandCBRequestCancel(LG_ClipboardRequest request,
|
||||
LG_ClipboardCancelReason reason)
|
||||
{
|
||||
(void)reason;
|
||||
LG_LOCK(wlCb.lock);
|
||||
struct ClipboardRead * data = wlCb.currentRead;
|
||||
if (!data || data->request != request)
|
||||
data = NULL;
|
||||
else
|
||||
clipboardReadTakeCurrentNL();
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
clipboardReadRetire(data);
|
||||
}
|
||||
|
||||
struct ClipboardWrite
|
||||
{
|
||||
int fd;
|
||||
size_t pos;
|
||||
struct CountedBuffer * buffer;
|
||||
LG_Lock lock;
|
||||
int fd;
|
||||
LG_ClipboardRequest request;
|
||||
LG_ClipboardData type;
|
||||
uint64_t offset;
|
||||
size_t pos;
|
||||
size_t pending;
|
||||
bool begun;
|
||||
bool ended;
|
||||
bool blocked;
|
||||
bool discard;
|
||||
bool pollOwned;
|
||||
bool pollRegistered;
|
||||
bool streamActive;
|
||||
bool requesting;
|
||||
uint8_t buffer[KVMFR_CLIPBOARD_DATA_BYTES];
|
||||
};
|
||||
|
||||
static void clipboardWriteDestroy(struct ClipboardWrite * data)
|
||||
{
|
||||
LG_LOCK_FREE(data->lock);
|
||||
free(data);
|
||||
}
|
||||
|
||||
static void clipboardWriteRetireStream(struct ClipboardWrite * data)
|
||||
{
|
||||
int fd = -1;
|
||||
bool destroy;
|
||||
LG_LOCK(data->lock);
|
||||
data->streamActive = false;
|
||||
if (data->pollRegistered)
|
||||
{
|
||||
data->pollRegistered = false;
|
||||
fd = data->fd;
|
||||
}
|
||||
destroy = !data->pollOwned && !data->requesting;
|
||||
LG_UNLOCK(data->lock);
|
||||
|
||||
if (fd >= 0)
|
||||
waylandPollUnregister(fd);
|
||||
else if (destroy)
|
||||
clipboardWriteDestroy(data);
|
||||
}
|
||||
|
||||
static LG_ClipboardResult clipboardWriteBegin(void * opaque,
|
||||
LG_ClipboardData type, uint64_t sizeHint)
|
||||
{
|
||||
(void)sizeHint;
|
||||
struct ClipboardWrite * data = opaque;
|
||||
LG_LOCK(data->lock);
|
||||
if (data->begun || data->ended || !data->streamActive ||
|
||||
type != data->type)
|
||||
{
|
||||
LG_UNLOCK(data->lock);
|
||||
return LG_CLIPBOARD_RESULT_FAILED;
|
||||
}
|
||||
|
||||
data->begun = true;
|
||||
LG_UNLOCK(data->lock);
|
||||
return LG_CLIPBOARD_RESULT_ACCEPTED;
|
||||
}
|
||||
|
||||
static LG_ClipboardResult clipboardWriteChunk(void * opaque,
|
||||
uint64_t offset, const void * buffer, size_t size)
|
||||
{
|
||||
struct ClipboardWrite * data = opaque;
|
||||
LG_LOCK(data->lock);
|
||||
if (!data->begun || data->ended || !data->streamActive ||
|
||||
!buffer || !size)
|
||||
{
|
||||
LG_UNLOCK(data->lock);
|
||||
return LG_CLIPBOARD_RESULT_FAILED;
|
||||
}
|
||||
if (data->pending)
|
||||
{
|
||||
data->blocked = true;
|
||||
LG_UNLOCK(data->lock);
|
||||
return LG_CLIPBOARD_RESULT_BLOCKED;
|
||||
}
|
||||
if (offset != data->offset)
|
||||
{
|
||||
LG_UNLOCK(data->lock);
|
||||
return LG_CLIPBOARD_RESULT_FAILED;
|
||||
}
|
||||
if (data->discard)
|
||||
{
|
||||
data->offset += size;
|
||||
LG_UNLOCK(data->lock);
|
||||
return LG_CLIPBOARD_RESULT_ACCEPTED;
|
||||
}
|
||||
if (size > sizeof(data->buffer))
|
||||
{
|
||||
LG_UNLOCK(data->lock);
|
||||
return LG_CLIPBOARD_RESULT_FAILED;
|
||||
}
|
||||
|
||||
memcpy(data->buffer, buffer, size);
|
||||
data->pending = size;
|
||||
data->pos = 0;
|
||||
if (!waylandPollUpdate(data->fd, EPOLLOUT))
|
||||
{
|
||||
data->discard = true;
|
||||
data->pending = 0;
|
||||
data->offset += size;
|
||||
}
|
||||
LG_UNLOCK(data->lock);
|
||||
return LG_CLIPBOARD_RESULT_ACCEPTED;
|
||||
}
|
||||
|
||||
static LG_ClipboardResult clipboardWriteEnd(void * opaque,
|
||||
uint64_t finalSize)
|
||||
{
|
||||
struct ClipboardWrite * data = opaque;
|
||||
LG_LOCK(data->lock);
|
||||
if (!data->begun || data->ended || !data->streamActive)
|
||||
{
|
||||
LG_UNLOCK(data->lock);
|
||||
return LG_CLIPBOARD_RESULT_FAILED;
|
||||
}
|
||||
if (data->pending)
|
||||
{
|
||||
data->blocked = true;
|
||||
LG_UNLOCK(data->lock);
|
||||
return LG_CLIPBOARD_RESULT_BLOCKED;
|
||||
}
|
||||
if (finalSize != data->offset)
|
||||
{
|
||||
LG_UNLOCK(data->lock);
|
||||
return LG_CLIPBOARD_RESULT_FAILED;
|
||||
}
|
||||
|
||||
data->ended = true;
|
||||
LG_UNLOCK(data->lock);
|
||||
clipboardWriteRetireStream(data);
|
||||
return LG_CLIPBOARD_RESULT_ACCEPTED;
|
||||
}
|
||||
|
||||
static void clipboardWriteCancel(void * opaque,
|
||||
LG_ClipboardCancelReason reason)
|
||||
{
|
||||
(void)reason;
|
||||
struct ClipboardWrite * data = opaque;
|
||||
clipboardWriteRetireStream(data);
|
||||
}
|
||||
|
||||
static const LG_ClipboardStreamOps clipboardWriteStream =
|
||||
{
|
||||
.begin = clipboardWriteBegin,
|
||||
.chunk = clipboardWriteChunk,
|
||||
.end = clipboardWriteEnd,
|
||||
.cancel = clipboardWriteCancel,
|
||||
};
|
||||
|
||||
static void clipboardWriteCleanup(void * opaque)
|
||||
{
|
||||
struct ClipboardWrite * data = opaque;
|
||||
LG_LOCK(data->lock);
|
||||
data->pollOwned = false;
|
||||
data->pollRegistered = false;
|
||||
close(data->fd);
|
||||
countedBufferRelease(&data->buffer);
|
||||
free(data);
|
||||
data->fd = -1;
|
||||
const bool destroy = !data->streamActive && !data->requesting;
|
||||
LG_UNLOCK(data->lock);
|
||||
if (destroy)
|
||||
clipboardWriteDestroy(data);
|
||||
}
|
||||
|
||||
static void clipboardWriteCallback(uint32_t events, void * opaque)
|
||||
{
|
||||
struct ClipboardWrite * data = opaque;
|
||||
if (events & EPOLLERR)
|
||||
if (events & (EPOLLERR | EPOLLHUP))
|
||||
goto error;
|
||||
|
||||
ssize_t written = write(data->fd, data->buffer->data + data->pos, data->buffer->size - data->pos);
|
||||
LG_LOCK(data->lock);
|
||||
if (!data->pending)
|
||||
{
|
||||
waylandPollUpdate(data->fd, 0);
|
||||
LG_UNLOCK(data->lock);
|
||||
return;
|
||||
}
|
||||
|
||||
const ssize_t written = write(data->fd, data->buffer + data->pos,
|
||||
data->pending - data->pos);
|
||||
if (written < 0)
|
||||
{
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK)
|
||||
{
|
||||
LG_UNLOCK(data->lock);
|
||||
return;
|
||||
}
|
||||
if (errno != EPIPE)
|
||||
DEBUG_ERROR("Failed to write clipboard data: %s", strerror(errno));
|
||||
LG_UNLOCK(data->lock);
|
||||
goto error;
|
||||
}
|
||||
|
||||
data->pos += written;
|
||||
if (data->pos < data->buffer->size)
|
||||
if (data->pos < data->pending)
|
||||
{
|
||||
LG_UNLOCK(data->lock);
|
||||
return;
|
||||
}
|
||||
|
||||
data->offset += data->pending;
|
||||
data->pending = 0;
|
||||
data->pos = 0;
|
||||
waylandPollUpdate(data->fd, 0);
|
||||
const bool ready = data->blocked;
|
||||
data->blocked = false;
|
||||
const LG_ClipboardRequest request = data->request;
|
||||
LG_UNLOCK(data->lock);
|
||||
if (ready)
|
||||
lgClipboard_requestReady(request);
|
||||
return;
|
||||
|
||||
error:
|
||||
waylandPollUnregister(data->fd);
|
||||
LG_LOCK(data->lock);
|
||||
data->discard = true;
|
||||
data->offset += data->pending;
|
||||
data->pending = 0;
|
||||
data->pos = 0;
|
||||
int fd = -1;
|
||||
if (data->pollRegistered)
|
||||
{
|
||||
data->pollRegistered = false;
|
||||
fd = data->fd;
|
||||
}
|
||||
const bool errorReady = data->blocked;
|
||||
data->blocked = false;
|
||||
const LG_ClipboardRequest errorRequest = data->request;
|
||||
LG_UNLOCK(data->lock);
|
||||
if (fd >= 0)
|
||||
waylandPollUnregister(fd);
|
||||
if (errorReady)
|
||||
lgClipboard_requestReady(errorRequest);
|
||||
}
|
||||
|
||||
static void dataSourceHandleTarget(void * data, struct wl_data_source * source,
|
||||
@@ -627,23 +1059,68 @@ static void dataSourceHandleSend(void * data, struct wl_data_source * source,
|
||||
struct WCBTransfer * transfer = (struct WCBTransfer *) data;
|
||||
if (containsMimetype(transfer->mimetypes, mimetype))
|
||||
{
|
||||
struct ClipboardWrite * data = malloc(sizeof(*data));
|
||||
struct ClipboardWrite * data = calloc(1, sizeof(*data));
|
||||
if (!data)
|
||||
{
|
||||
DEBUG_ERROR("Out of memory trying to allocate ClipboardWrite");
|
||||
goto error;
|
||||
}
|
||||
|
||||
data->fd = fd;
|
||||
data->pos = 0;
|
||||
data->buffer = transfer->data;
|
||||
countedBufferAddRef(transfer->data);
|
||||
data->fd = fd;
|
||||
data->request = LG_CLIPBOARD_REQUEST_INVALID;
|
||||
data->type = transfer->type;
|
||||
LG_LOCK_INIT(data->lock);
|
||||
const int flags = fcntl(fd, F_GETFL);
|
||||
const int fdFlags = fcntl(fd, F_GETFD);
|
||||
if (flags < 0 || fdFlags < 0 ||
|
||||
fcntl(fd, F_SETFD, fdFlags | FD_CLOEXEC) < 0 ||
|
||||
fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to make clipboard pipe nonblocking: %s",
|
||||
strerror(errno));
|
||||
clipboardWriteDestroy(data);
|
||||
goto error;
|
||||
}
|
||||
data->pollOwned = true;
|
||||
data->pollRegistered = true;
|
||||
data->streamActive = true;
|
||||
if (waylandPollRegisterWithCleanup(fd,
|
||||
clipboardWriteCallback, data, clipboardWriteCleanup, EPOLLOUT))
|
||||
return;
|
||||
clipboardWriteCallback, data, clipboardWriteCleanup, 0))
|
||||
{
|
||||
LG_LOCK(data->lock);
|
||||
data->requesting = true;
|
||||
LG_UNLOCK(data->lock);
|
||||
const bool result = lgClipboard_requestStream(transfer->type,
|
||||
&clipboardWriteStream, data, &data->request);
|
||||
|
||||
countedBufferRelease(&data->buffer);
|
||||
free(data);
|
||||
int retireFd = -1;
|
||||
bool destroy = false;
|
||||
LG_LOCK(data->lock);
|
||||
data->requesting = false;
|
||||
if (!result || data->request == LG_CLIPBOARD_REQUEST_INVALID ||
|
||||
!data->streamActive)
|
||||
{
|
||||
data->streamActive = false;
|
||||
if (data->pollRegistered)
|
||||
{
|
||||
data->pollRegistered = false;
|
||||
retireFd = data->fd;
|
||||
}
|
||||
destroy = !data->pollOwned;
|
||||
}
|
||||
LG_UNLOCK(data->lock);
|
||||
|
||||
if (retireFd >= 0)
|
||||
waylandPollUnregister(retireFd);
|
||||
else if (destroy)
|
||||
clipboardWriteDestroy(data);
|
||||
return;
|
||||
}
|
||||
|
||||
data->pollOwned = false;
|
||||
data->pollRegistered = false;
|
||||
data->streamActive = false;
|
||||
clipboardWriteDestroy(data);
|
||||
}
|
||||
|
||||
error:
|
||||
@@ -654,7 +1131,18 @@ static void dataSourceHandleCancelled(void * data,
|
||||
struct wl_data_source * source)
|
||||
{
|
||||
struct WCBTransfer * transfer = (struct WCBTransfer *) data;
|
||||
countedBufferRelease(&transfer->data);
|
||||
DEBUG_ASSERT(transfer->source == source);
|
||||
|
||||
LG_LOCK(wlCb.lock);
|
||||
struct WCBTransfer ** link = &wlCb.sources;
|
||||
while (*link && *link != transfer)
|
||||
link = &(*link)->next;
|
||||
if (*link)
|
||||
*link = transfer->next;
|
||||
if (wlCb.selectionSource == source)
|
||||
wlCb.selectionSource = NULL;
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
|
||||
free(transfer);
|
||||
wl_data_source_destroy(source);
|
||||
}
|
||||
@@ -665,12 +1153,8 @@ static const struct wl_data_source_listener dataSourceListener = {
|
||||
.cancelled = dataSourceHandleCancelled,
|
||||
};
|
||||
|
||||
static void waylandCBReplyFn(void * opaque, LG_ClipboardData type,
|
||||
const uint8_t * data, uint32_t size)
|
||||
static void waylandCBPublish(LG_ClipboardData type)
|
||||
{
|
||||
if (type == LG_CLIPBOARD_DATA_NONE)
|
||||
return;
|
||||
|
||||
struct WCBTransfer * transfer = malloc(sizeof(*transfer));
|
||||
if (!transfer)
|
||||
{
|
||||
@@ -679,35 +1163,61 @@ static void waylandCBReplyFn(void * opaque, LG_ClipboardData type,
|
||||
}
|
||||
|
||||
transfer->mimetypes = cbTypeToMimetypes(type);
|
||||
transfer->data = countedBufferNew(size);
|
||||
if (!transfer->data)
|
||||
{
|
||||
DEBUG_ERROR("Out of memory when allocating clipboard buffer");
|
||||
free(transfer);
|
||||
return;
|
||||
}
|
||||
if (size)
|
||||
memcpy(transfer->data->data, data, size);
|
||||
transfer->type = type;
|
||||
transfer->next = NULL;
|
||||
|
||||
struct wl_data_source * source =
|
||||
wl_data_device_manager_create_data_source(wlWm.dataDeviceManager);
|
||||
wl_data_source_add_listener(source, &dataSourceListener, transfer);
|
||||
if (!source)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create clipboard data source");
|
||||
free(transfer);
|
||||
return;
|
||||
}
|
||||
transfer->source = source;
|
||||
if (wl_data_source_add_listener(source, &dataSourceListener, transfer) < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to listen to clipboard data source");
|
||||
wl_data_source_destroy(source);
|
||||
free(transfer);
|
||||
return;
|
||||
}
|
||||
for (const char ** mimetype = transfer->mimetypes; *mimetype; mimetype++)
|
||||
wl_data_source_offer(source, *mimetype);
|
||||
wl_data_source_offer(source, wlCb.lgMimetype);
|
||||
|
||||
wl_data_device_set_selection(wlCb.dataDevice, source,
|
||||
wlWm.keyboardEnterSerial);
|
||||
LG_LOCK(wlCb.lock);
|
||||
if (wlCb.dataDevice)
|
||||
{
|
||||
transfer->next = wlCb.sources;
|
||||
wlCb.sources = transfer;
|
||||
wlCb.selectionSource = source;
|
||||
wl_data_device_set_selection(wlCb.dataDevice, source,
|
||||
wlWm.keyboardEnterSerial);
|
||||
}
|
||||
else
|
||||
{
|
||||
wl_data_source_destroy(source);
|
||||
free(transfer);
|
||||
}
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
}
|
||||
|
||||
void waylandCBNotice(LG_ClipboardData type)
|
||||
{
|
||||
if (!app_clipboardRequest(type, waylandCBReplyFn, NULL))
|
||||
DEBUG_ERROR("Failed to request remote clipboard data");
|
||||
waylandCBPublish(type);
|
||||
}
|
||||
|
||||
void waylandCBRelease(void)
|
||||
{
|
||||
LG_LOCK(wlCb.lock);
|
||||
if (wlCb.dataDevice && wlCb.selectionSource)
|
||||
{
|
||||
wlCb.selectionSource = NULL;
|
||||
wl_data_device_set_selection(wlCb.dataDevice, NULL,
|
||||
wlWm.keyboardEnterSerial);
|
||||
}
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
}
|
||||
|
||||
void waylandCBFree(void)
|
||||
@@ -718,9 +1228,15 @@ void waylandCBFree(void)
|
||||
struct wl_data_offer * offer = wlCb.offer;
|
||||
struct wl_data_offer * dndOffer = wlCb.dndOffer;
|
||||
struct wl_data_device * dataDevice = wlCb.dataDevice;
|
||||
struct WCBTransfer * sources = wlCb.sources;
|
||||
if (dataDevice && wlCb.selectionSource)
|
||||
wl_data_device_set_selection(dataDevice, NULL,
|
||||
wlWm.keyboardEnterSerial);
|
||||
wlCb.offer = NULL;
|
||||
wlCb.dndOffer = NULL;
|
||||
wlCb.dataDevice = NULL;
|
||||
wlCb.selectionSource = NULL;
|
||||
wlCb.sources = NULL;
|
||||
memcpy(mimetypes, wlCb.mimetypes, sizeof(mimetypes));
|
||||
memset(wlCb.mimetypes, 0, sizeof(wlCb.mimetypes));
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
@@ -732,6 +1248,13 @@ void waylandCBFree(void)
|
||||
wl_data_offer_destroy(dndOffer);
|
||||
if (dataDevice)
|
||||
wl_data_device_release(dataDevice);
|
||||
while (sources)
|
||||
{
|
||||
struct WCBTransfer * next = sources->next;
|
||||
wl_data_source_destroy(sources->source);
|
||||
free(sources);
|
||||
sources = next;
|
||||
}
|
||||
for (enum LG_ClipboardData i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
free(mimetypes[i]);
|
||||
|
||||
|
||||
@@ -133,16 +133,18 @@ bool waylandPollRegisterWithCleanup(int fd, WaylandPollCallback callback,
|
||||
return false;
|
||||
|
||||
node->fd = fd;
|
||||
node->events = events;
|
||||
node->callback = callback;
|
||||
node->cleanup = cleanup;
|
||||
node->opaque = opaque;
|
||||
atomic_init(&node->removed, false);
|
||||
|
||||
LG_LOCK(wlWm.pollLock);
|
||||
if (epoll_ctl(wlWm.epollFd, EPOLL_CTL_ADD, fd, &(struct epoll_event) {
|
||||
.events = events,
|
||||
.data = (epoll_data_t) { .ptr = node },
|
||||
}) < 0)
|
||||
if (events && epoll_ctl(wlWm.epollFd, EPOLL_CTL_ADD, fd,
|
||||
&(struct epoll_event) {
|
||||
.events = events,
|
||||
.data = (epoll_data_t) { .ptr = node },
|
||||
}) < 0)
|
||||
{
|
||||
LG_UNLOCK(wlWm.pollLock);
|
||||
free(node);
|
||||
@@ -184,7 +186,8 @@ bool waylandPollUnregister(int fd)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (epoll_ctl(wlWm.epollFd, EPOLL_CTL_DEL, fd, NULL) < 0)
|
||||
if (node->events &&
|
||||
epoll_ctl(wlWm.epollFd, EPOLL_CTL_DEL, fd, NULL) < 0)
|
||||
{
|
||||
LG_UNLOCK(wlWm.pollLock);
|
||||
DEBUG_ERROR("Failed to unregister from epoll: %s", strerror(errno));
|
||||
@@ -199,3 +202,45 @@ bool waylandPollUnregister(int fd)
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool waylandPollUpdate(int fd, uint32_t events)
|
||||
{
|
||||
bool result = false;
|
||||
LG_LOCK(wlWm.pollLock);
|
||||
struct WaylandPoll * node;
|
||||
wl_list_for_each(node, &wlWm.poll, link)
|
||||
{
|
||||
if (node->fd != fd)
|
||||
continue;
|
||||
|
||||
int operation;
|
||||
if (!node->events && events)
|
||||
operation = EPOLL_CTL_ADD;
|
||||
else if (node->events && !events)
|
||||
operation = EPOLL_CTL_DEL;
|
||||
else if (events)
|
||||
operation = EPOLL_CTL_MOD;
|
||||
else
|
||||
{
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
|
||||
struct epoll_event event =
|
||||
{
|
||||
.events = events,
|
||||
.data = (epoll_data_t) { .ptr = node },
|
||||
};
|
||||
if (epoll_ctl(wlWm.epollFd, operation, fd,
|
||||
operation == EPOLL_CTL_DEL ? NULL : &event) < 0)
|
||||
DEBUG_ERROR("Failed to update epoll events: %s", strerror(errno));
|
||||
else
|
||||
{
|
||||
node->events = events;
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
LG_UNLOCK(wlWm.pollLock);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -355,8 +355,10 @@ struct LG_DisplayServerOps LGDS_Wayland =
|
||||
|
||||
.setHDRImageDesc = waylandHDRCallback,
|
||||
|
||||
.cbInit = waylandCBInit,
|
||||
.cbNotice = waylandCBNotice,
|
||||
.cbRelease = waylandCBRelease,
|
||||
.cbRequest = waylandCBRequest
|
||||
.cbInit = waylandCBInit,
|
||||
.cbNotice = waylandCBNotice,
|
||||
.cbRelease = waylandCBRelease,
|
||||
.cbRequest = waylandCBRequest,
|
||||
.cbRequestReady = waylandCBRequestReady,
|
||||
.cbRequestCancel = waylandCBRequestCancel,
|
||||
};
|
||||
|
||||
@@ -35,8 +35,8 @@
|
||||
|
||||
#include "app.h"
|
||||
#include "egl_dynprocs.h"
|
||||
#include "common/KVMFRClipboard.h"
|
||||
#include "common/locking.h"
|
||||
#include "common/countedbuffer.h"
|
||||
#include "common/ringbuffer.h"
|
||||
#include "interface/displayserver.h"
|
||||
#include "interface/desktop.h"
|
||||
@@ -65,6 +65,7 @@ typedef void (*WaylandPollCleanup)(void * opaque);
|
||||
struct WaylandPoll
|
||||
{
|
||||
int fd;
|
||||
uint32_t events;
|
||||
atomic_bool removed;
|
||||
WaylandPollCallback callback;
|
||||
WaylandPollCleanup cleanup;
|
||||
@@ -286,18 +287,26 @@ struct WaylandDSState
|
||||
|
||||
struct WCBTransfer
|
||||
{
|
||||
struct CountedBuffer * data;
|
||||
const char ** mimetypes;
|
||||
LG_ClipboardData type;
|
||||
const char ** mimetypes;
|
||||
struct wl_data_source * source;
|
||||
struct WCBTransfer * next;
|
||||
};
|
||||
|
||||
struct ClipboardRead
|
||||
{
|
||||
int fd;
|
||||
size_t size;
|
||||
size_t numRead;
|
||||
uint8_t * buf;
|
||||
LG_ClipboardRequest request;
|
||||
LG_ClipboardData type;
|
||||
atomic_uint references;
|
||||
int fd;
|
||||
LG_ClipboardRequest request;
|
||||
LG_ClipboardData type;
|
||||
uint64_t offset;
|
||||
size_t pending;
|
||||
bool begun;
|
||||
bool blocked;
|
||||
bool calling;
|
||||
bool readyPending;
|
||||
bool eof;
|
||||
uint8_t buffer[KVMFR_CLIPBOARD_DATA_BYTES];
|
||||
};
|
||||
|
||||
struct WCBState
|
||||
@@ -309,6 +318,9 @@ struct WCBState
|
||||
struct wl_data_offer * offer;
|
||||
struct wl_data_offer * dndOffer;
|
||||
|
||||
struct wl_data_source * selectionSource;
|
||||
struct WCBTransfer * sources;
|
||||
|
||||
LG_Lock lock;
|
||||
struct ClipboardRead * currentRead;
|
||||
};
|
||||
@@ -325,6 +337,9 @@ void waylandActivationRequestActivation(void);
|
||||
bool waylandCBInit(void);
|
||||
void waylandCBFree(void);
|
||||
void waylandCBRequest(LG_ClipboardRequest request, LG_ClipboardData type);
|
||||
void waylandCBRequestReady(LG_ClipboardRequest request);
|
||||
void waylandCBRequestCancel(LG_ClipboardRequest request,
|
||||
LG_ClipboardCancelReason reason);
|
||||
void waylandCBNotice(LG_ClipboardData type);
|
||||
void waylandCBRelease(void);
|
||||
void waylandCBInvalidate(void);
|
||||
@@ -417,6 +432,7 @@ bool waylandPollRegister(int fd, WaylandPollCallback callback, void * opaque, ui
|
||||
bool waylandPollRegisterWithCleanup(int fd, WaylandPollCallback callback,
|
||||
void * opaque, WaylandPollCleanup cleanup, uint32_t events);
|
||||
bool waylandPollUnregister(int fd);
|
||||
bool waylandPollUpdate(int fd, uint32_t events);
|
||||
|
||||
// presentation module
|
||||
struct WaylandPresentationFrame;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -32,5 +32,8 @@ bool x11CBInit(void);
|
||||
void x11CBNotice(LG_ClipboardData type);
|
||||
void x11CBRelease(void);
|
||||
void x11CBRequest(LG_ClipboardRequest request, LG_ClipboardData type);
|
||||
void x11CBRequestReady(LG_ClipboardRequest request);
|
||||
void x11CBRequestCancel(LG_ClipboardRequest request,
|
||||
LG_ClipboardCancelReason reason);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -2314,8 +2314,10 @@ struct LG_DisplayServerOps LGDS_X11 =
|
||||
.getFullscreen = x11GetFullscreen,
|
||||
.minimize = x11Minimize,
|
||||
|
||||
.cbInit = x11CBInit,
|
||||
.cbNotice = x11CBNotice,
|
||||
.cbRelease = x11CBRelease,
|
||||
.cbRequest = x11CBRequest
|
||||
.cbInit = x11CBInit,
|
||||
.cbNotice = x11CBNotice,
|
||||
.cbRelease = x11CBRelease,
|
||||
.cbRequest = x11CBRequest,
|
||||
.cbRequestReady = x11CBRequestReady,
|
||||
.cbRequestCancel = x11CBRequestCancel,
|
||||
};
|
||||
|
||||
@@ -173,14 +173,6 @@ void app_overlayConfigRegister(const char * title,
|
||||
void app_overlayConfigRegisterTab(const char * title,
|
||||
void (*callback)(void * udata, int * id), void * udata);
|
||||
|
||||
void app_clipboardRelease(void);
|
||||
void app_clipboardNotifyTypes(const LG_ClipboardData types[], int count);
|
||||
void app_clipboardData(LG_ClipboardRequest request,
|
||||
LG_ClipboardData type, const void * data, size_t size);
|
||||
void app_clipboardAbort(LG_ClipboardRequest request);
|
||||
bool app_clipboardRequest(LG_ClipboardData type,
|
||||
LG_ClipboardReplyFn replyFn, void * opaque);
|
||||
|
||||
/**
|
||||
* Show an alert on screen
|
||||
* @param type The alert type
|
||||
|
||||
@@ -66,6 +66,7 @@ enum
|
||||
LG_TRANSPORT_FEATURE_WINDOW_SIZE = 0x2,
|
||||
LG_TRANSPORT_FEATURE_FRAME_SCHEDULE = 0x4,
|
||||
LG_TRANSPORT_FEATURE_INPUT = 0x8,
|
||||
LG_TRANSPORT_FEATURE_CLIPBOARD = 0x10,
|
||||
};
|
||||
|
||||
typedef uint32_t LG_TransportFeatureFlags;
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
#include "main.h"
|
||||
#include "core.h"
|
||||
#include "util.h"
|
||||
#include "clipboard.h"
|
||||
#include "render_queue.h"
|
||||
#include "evdev.h"
|
||||
#include "input.h"
|
||||
@@ -349,35 +348,6 @@ void app_handleEnterEvent(bool entered)
|
||||
}
|
||||
}
|
||||
|
||||
void app_clipboardRelease(void)
|
||||
{
|
||||
lgClipboard_release();
|
||||
}
|
||||
|
||||
void app_clipboardNotifyTypes(const LG_ClipboardData types[], int count)
|
||||
{
|
||||
if (count < 0)
|
||||
return;
|
||||
lgClipboard_notifyTypes(types, (size_t)count);
|
||||
}
|
||||
|
||||
void app_clipboardData(LG_ClipboardRequest request,
|
||||
const LG_ClipboardData type, const void * data, size_t size)
|
||||
{
|
||||
lgClipboard_data(request, type, data, size);
|
||||
}
|
||||
|
||||
void app_clipboardAbort(LG_ClipboardRequest request)
|
||||
{
|
||||
lgClipboard_abort(request);
|
||||
}
|
||||
|
||||
bool app_clipboardRequest(LG_ClipboardData type,
|
||||
LG_ClipboardReplyFn replyFn, void * opaque)
|
||||
{
|
||||
return lgClipboard_request(type, replyFn, opaque);
|
||||
}
|
||||
|
||||
static int mapInputToImGuiButton(uint32_t button)
|
||||
{
|
||||
switch (button)
|
||||
|
||||
@@ -1761,6 +1761,11 @@ static bool requestClipboard(LG_ClipboardData type,
|
||||
|
||||
const bool queued = available &&
|
||||
ll_push(clipboard.requests, request);
|
||||
/* Publish the request before the provider can make it visible to its
|
||||
* worker. A provider event may otherwise reach the stream sink before the
|
||||
* caller has learned which request it belongs to. */
|
||||
if (queued && resultId)
|
||||
*resultId = id;
|
||||
bool result = queued;
|
||||
if (queued)
|
||||
result = clipboard.active.ops->request(clipboard.active.opaque,
|
||||
@@ -1769,20 +1774,14 @@ static bool requestClipboard(LG_ClipboardData type,
|
||||
LG_UNLOCK_SHARED(clipboard.activeLock);
|
||||
|
||||
if (result)
|
||||
{
|
||||
if (resultId)
|
||||
*resultId = id;
|
||||
return true;
|
||||
}
|
||||
if (queued)
|
||||
{
|
||||
ClipboardRequest * failed = takeRequest(id);
|
||||
if (!failed)
|
||||
{
|
||||
if (resultId)
|
||||
*resultId = id;
|
||||
return true;
|
||||
}
|
||||
if (resultId)
|
||||
*resultId = LG_CLIPBOARD_REQUEST_INVALID;
|
||||
freeRequest(failed);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,9 @@ LG_ClipboardResult lgClipboard_dataEnd(LG_ClipboardRequest request,
|
||||
void lgClipboard_data(LG_ClipboardRequest request,
|
||||
LG_ClipboardData type, const void * data, size_t size);
|
||||
void lgClipboard_abort(LG_ClipboardRequest request);
|
||||
/* request is published before provider dispatch. Its storage must remain
|
||||
* valid until this function returns and may be observed by stream callbacks
|
||||
* before then. */
|
||||
bool lgClipboard_requestStream(LG_ClipboardData type,
|
||||
const LG_ClipboardStreamOps * stream, void * opaque,
|
||||
LG_ClipboardRequest * request);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 },
|
||||
};
|
||||
|
||||
|
||||
1078
client/tests/lgmp_clipboard_test.c
Normal file
1078
client/tests/lgmp_clipboard_test.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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 },
|
||||
};
|
||||
|
||||
@@ -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 },
|
||||
};
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.10)
|
||||
project(transport_LGMP LANGUAGES C)
|
||||
|
||||
add_library(transport_LGMP STATIC
|
||||
clipboard.c
|
||||
input.c
|
||||
lgmp.c
|
||||
)
|
||||
|
||||
1679
client/transports/LGMP/clipboard.c
Normal file
1679
client/transports/LGMP/clipboard.c
Normal file
File diff suppressed because it is too large
Load Diff
41
client/transports/LGMP/clipboard.h
Normal file
41
client/transports/LGMP/clipboard.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 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_TRANSPORT_LGMP_CLIPBOARD_
|
||||
#define _H_LG_CLIENT_TRANSPORT_LGMP_CLIPBOARD_
|
||||
|
||||
#include "interface/clipboard.h"
|
||||
|
||||
#include <lgmp/client.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct LGMPClipboard LGMPClipboard;
|
||||
|
||||
bool lgmpClipboard_create(PLGMPClient client, LGMPClipboard ** result);
|
||||
void lgmpClipboard_destroy(LGMPClipboard ** clipboard);
|
||||
|
||||
bool lgmpClipboard_connect(LGMPClipboard * clipboard, uint32_t clientID);
|
||||
void lgmpClipboard_disconnect(LGMPClipboard * clipboard);
|
||||
|
||||
const LG_ClipboardOps * lgmpClipboard_getOps(void);
|
||||
|
||||
#endif
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#include "interface/transport.h"
|
||||
|
||||
#include "clipboard.h"
|
||||
#include "input.h"
|
||||
|
||||
#include "common/KVMFR.h"
|
||||
@@ -109,6 +110,7 @@ struct LG_Transport
|
||||
PLGMPClientQueue ownerFrameQueue[LGMP_Q_FRAME_LEN];
|
||||
PLGMPClientQueue pointerQueue;
|
||||
LGMPInput * input;
|
||||
LGMPClipboard * clipboard;
|
||||
LG_Lock frameLock;
|
||||
LG_Lock pointerLock;
|
||||
LG_RWLock videoStatusLock;
|
||||
@@ -133,6 +135,7 @@ struct LG_Transport
|
||||
bool frameStopRequested;
|
||||
bool frameScheduleSupported;
|
||||
atomic_bool inputSupported;
|
||||
atomic_bool clipboardSupported;
|
||||
uint64_t frameLeaseHandle;
|
||||
uint32_t frameGeneration;
|
||||
uint32_t clientID;
|
||||
@@ -628,6 +631,13 @@ static LGMP_STATUS lgmp_initializeClient(struct LG_Transport * this)
|
||||
return LGMP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
if (!lgmpClipboard_create(this->client, &this->clipboard))
|
||||
{
|
||||
lgmpInput_destroy(&this->input);
|
||||
lgmpClientFree(&this->client);
|
||||
return LGMP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
return LGMP_OK;
|
||||
}
|
||||
|
||||
@@ -650,6 +660,7 @@ static bool lgmp_create(LG_Transport ** result)
|
||||
this->cursorPollInterval = cursorPoll;
|
||||
atomic_init(&this->connected, false);
|
||||
atomic_init(&this->inputSupported, false);
|
||||
atomic_init(&this->clipboardSupported, false);
|
||||
atomic_init(&this->destroyPending, false);
|
||||
|
||||
this->frameWake = lgCreateEvent(true, 0);
|
||||
@@ -860,6 +871,7 @@ static void lgmp_destroyNow(LG_Transport * this)
|
||||
{
|
||||
if (this->client)
|
||||
{
|
||||
lgmpClipboard_destroy(&this->clipboard);
|
||||
lgmpInput_destroy(&this->input);
|
||||
lgmp_closeQueues(this);
|
||||
lgmpClientFree(&this->client);
|
||||
@@ -958,6 +970,8 @@ static bool lgmp_parseSession(struct LG_Transport * this,
|
||||
session->features |= LG_TRANSPORT_FEATURE_FRAME_SCHEDULE;
|
||||
if (header->features & KVMFR_FEATURE_INPUT)
|
||||
session->features |= LG_TRANSPORT_FEATURE_INPUT;
|
||||
if (header->features & KVMFR_FEATURE_CLIPBOARD)
|
||||
session->features |= LG_TRANSPORT_FEATURE_CLIPBOARD;
|
||||
|
||||
data += sizeof(*header);
|
||||
size -= sizeof(*header);
|
||||
@@ -1076,6 +1090,9 @@ static LG_TransportStatus lgmp_connectInternal(LG_Transport * this,
|
||||
atomic_store_explicit(&this->inputSupported,
|
||||
session->features & LG_TRANSPORT_FEATURE_INPUT,
|
||||
memory_order_release);
|
||||
atomic_store_explicit(&this->clipboardSupported,
|
||||
session->features & LG_TRANSPORT_FEATURE_CLIPBOARD,
|
||||
memory_order_release);
|
||||
this->frameSerial = 0;
|
||||
this->frameSerialValid = false;
|
||||
this->formatValid = false;
|
||||
@@ -1136,6 +1153,7 @@ static void lgmp_disconnect(LG_Transport * this)
|
||||
lgmp_retainVideoStatusLifetime(this);
|
||||
|
||||
lgmpInput_disconnect(this->input);
|
||||
lgmpClipboard_disconnect(this->clipboard);
|
||||
|
||||
LG_LOCK(this->frameLock);
|
||||
if (++this->frameGeneration == 0)
|
||||
@@ -1145,6 +1163,8 @@ static void lgmp_disconnect(LG_Transport * this)
|
||||
this->frameScheduleSupported = false;
|
||||
atomic_store_explicit(
|
||||
&this->inputSupported, false, memory_order_release);
|
||||
atomic_store_explicit(
|
||||
&this->clipboardSupported, false, memory_order_release);
|
||||
this->clientID = 0;
|
||||
LG_UNLOCK(this->frameLock);
|
||||
|
||||
@@ -2474,6 +2494,20 @@ static const LG_InputOps * lgmp_getInputOps(LG_Transport * this,
|
||||
return lgmpInput_getOps();
|
||||
}
|
||||
|
||||
static const LG_ClipboardOps * lgmp_getClipboardOps(
|
||||
LG_Transport * this, void ** opaque)
|
||||
{
|
||||
*opaque = NULL;
|
||||
if (!atomic_load_explicit(&this->connected, memory_order_acquire) ||
|
||||
!atomic_load_explicit(
|
||||
&this->clipboardSupported, memory_order_acquire) ||
|
||||
!lgmpClipboard_connect(this->clipboard, this->clientID))
|
||||
return NULL;
|
||||
|
||||
*opaque = this->clipboard;
|
||||
return lgmpClipboard_getOps();
|
||||
}
|
||||
|
||||
static void lgmp_probeVideoStatus(LG_Transport * this)
|
||||
{
|
||||
bool frameAvailable = false;
|
||||
@@ -2617,6 +2651,7 @@ const LG_TransportOps LGT_LGMP =
|
||||
.sessionValid = lgmp_sessionValid,
|
||||
.getVideoOps = lgmp_getVideoOps,
|
||||
.getInputOps = lgmp_getInputOps,
|
||||
.getClipboardOps = lgmp_getClipboardOps,
|
||||
.getRecoveryInfo = lgmp_getRecoveryInfo,
|
||||
.requestRecovery = lgmp_requestRecovery,
|
||||
.sendControl = lgmp_sendControl,
|
||||
|
||||
Reference in New Issue
Block a user