From 800f063a1d22345624158f6d32a2083e6d636b62 Mon Sep 17 00:00:00 2001 From: Quantum Date: Thu, 11 Feb 2021 02:52:17 -0500 Subject: [PATCH] [client] wayland: make clipboard read async This allows reading from the clipboard without blocking the UI thread. --- client/displayservers/Wayland/wayland.c | 170 +++++++++++++++++------- 1 file changed, 119 insertions(+), 51 deletions(-) diff --git a/client/displayservers/Wayland/wayland.c b/client/displayservers/Wayland/wayland.c index b2dee43a..f6aeb5cb 100644 --- a/client/displayservers/Wayland/wayland.c +++ b/client/displayservers/Wayland/wayland.c @@ -133,17 +133,32 @@ struct WCBTransfer const char ** mimetypes; }; +struct ClipboardRead +{ + int fd; + size_t size; + size_t numRead; + uint8_t * buf; + enum LG_ClipboardData type; + struct wl_data_offer * offer; +}; + struct WCBState { - enum LG_ClipboardData stashedType; - char * stashedMimetype; - uint8_t * stashedContents; - ssize_t stashedSize; - bool isSelfCopy; char lgMimetype[64]; + enum LG_ClipboardData pendingType; + char * pendingMimetype; + bool isSelfCopy; + + enum LG_ClipboardData stashedType; + uint8_t * stashedContents; + ssize_t stashedSize; + bool haveRequest; - LG_ClipboardData type; + LG_ClipboardData type; + + struct ClipboardRead * currentRead; }; static struct WaylandDSState wm; @@ -1135,13 +1150,13 @@ static void dataOfferHandleOffer(void * data, struct wl_data_offer * offer, // Since we can't copy or paste rich text, we should instead prefer actual // images or plain text. if (type != LG_CLIPBOARD_DATA_NONE && - (wcb.stashedType == LG_CLIPBOARD_DATA_NONE || - strstr(wcb.stashedMimetype, "html"))) + (wcb.pendingType == LG_CLIPBOARD_DATA_NONE || + strstr(wcb.pendingMimetype, "html"))) { - wcb.stashedType = type; - if (wcb.stashedMimetype) - free(wcb.stashedMimetype); - wcb.stashedMimetype = strdup(mimetype); + wcb.pendingType = type; + if (wcb.pendingMimetype) + free(wcb.pendingMimetype); + wcb.pendingMimetype = strdup(mimetype); } if (!strcmp(mimetype, wcb.lgMimetype)) @@ -1169,17 +1184,75 @@ static const struct wl_data_offer_listener dataOfferListener = { static void dataDeviceHandleDataOffer(void * data, struct wl_data_device * dataDevice, struct wl_data_offer * offer) { - wcb.stashedType = LG_CLIPBOARD_DATA_NONE; + wcb.pendingType = LG_CLIPBOARD_DATA_NONE; wcb.isSelfCopy = false; wl_data_offer_add_listener(offer, &dataOfferListener, NULL); } -static void dataDeviceHandleSelection(void * data, +static void clipboardReadCancel(struct ClipboardRead * data, bool freeBuf) +{ + waylandEpollUnregister(data->fd); + close(data->fd); + wl_data_offer_destroy(data->offer); + if (freeBuf) + free(data->buf); + free(data); + wcb.currentRead = NULL; +} + +static void clipboardReadCallback(uint32_t events, void * opaque) +{ + struct ClipboardRead * data = opaque; + if (events & EPOLLERR) + { + clipboardReadCancel(data, true); + return; + } + + ssize_t result = read(data->fd, data->buf + data->numRead, data->size - data->numRead); + if (result < 0) + { + DEBUG_ERROR("Failed to read from clipboard: %s", strerror(errno)); + clipboardReadCancel(data, true); + return; + } + + if (result == 0) + { + data->buf[data->numRead] = 0; + wcb.stashedType = data->type; + wcb.stashedSize = data->numRead; + wcb.stashedContents = data->buf; + + clipboardReadCancel(data, false); + app_clipboardNotify(wcb.stashedType, 0); + return; + } + + data->numRead += result; + if (data->numRead >= data->size) + { + data->size *= 2; + void * nbuf = realloc(data->buf, data->size); + if (!nbuf) { + DEBUG_ERROR("Failed to realloc clipboard buffer: %s", strerror(errno)); + clipboardReadCancel(data, true); + return; + } + + data->buf = nbuf; + } +} + +static void dataDeviceHandleSelection(void * opaque, struct wl_data_device * dataDevice, struct wl_data_offer * offer) { - if (wcb.stashedType == LG_CLIPBOARD_DATA_NONE || wcb.isSelfCopy || !offer) + if (wcb.pendingType == LG_CLIPBOARD_DATA_NONE || wcb.isSelfCopy || !offer) return; + if (wcb.currentRead) + clipboardReadCancel(wcb.currentRead, true); + int fds[2]; if (pipe(fds) < 0) { @@ -1187,10 +1260,10 @@ static void dataDeviceHandleSelection(void * data, abort(); } - wl_data_offer_receive(offer, wcb.stashedMimetype, fds[1]); + wl_data_offer_receive(offer, wcb.pendingMimetype, fds[1]); close(fds[1]); - free(wcb.stashedMimetype); - wcb.stashedMimetype = NULL; + free(wcb.pendingMimetype); + wcb.pendingMimetype = NULL; wl_display_roundtrip(wm.display); @@ -1200,43 +1273,38 @@ static void dataDeviceHandleSelection(void * data, wcb.stashedContents = NULL; } - size_t size = 4096, numRead = 0; - uint8_t * buf = (uint8_t *) malloc(size); - while (true) + struct ClipboardRead * data = malloc(sizeof(struct ClipboardRead)); + if (!data) { - ssize_t result = read(fds[0], buf + numRead, size - numRead); - if (result < 0) - { - DEBUG_ERROR("Failed to read from clipboard: %s", strerror(errno)); - abort(); - } - - if (result == 0) - { - buf[numRead] = 0; - break; - } - - numRead += result; - if (numRead >= size) - { - size *= 2; - void * nbuf = realloc(buf, size); - if (!nbuf) { - DEBUG_ERROR("Failed to realloc clipboard buffer: %s", strerror(errno)); - abort(); - } - - buf = nbuf; - } + DEBUG_ERROR("Failed to allocate memory to read clipboard"); + close(fds[0]); + return; } - wcb.stashedSize = numRead; - wcb.stashedContents = buf; + data->fd = fds[0]; + data->size = 4096; + data->numRead = 0; + data->buf = malloc(data->size); + data->offer = offer; + data->type = wcb.pendingType; - close(fds[0]); - wl_data_offer_destroy(offer); - app_clipboardNotify(wcb.stashedType, 0); + if (!data->buf) + { + DEBUG_ERROR("Failed to allocate memory to receive clipboard data"); + close(data->fd); + free(data); + return; + } + + if (!waylandEpollRegister(data->fd, clipboardReadCallback, data, EPOLLIN)) + { + DEBUG_ERROR("Failed to register clipboard read into epoll: %s", strerror(errno)); + close(data->fd); + free(data->buf); + free(data); + } + + wcb.currentRead = data; } static const struct wl_data_device_listener dataDeviceListener = {