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;
|
||||
|
||||
Reference in New Issue
Block a user