[client] clipboard: add direct LGMP synchronization

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

View File

@@ -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]);

View File

@@ -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;
}

View File

@@ -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,
};

View File

@@ -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;