[client] wayland: make clipboard read async

This allows reading from the clipboard without blocking the UI thread.
This commit is contained in:
Quantum 2021-02-11 02:52:17 -05:00 committed by Geoffrey McRae
parent e01666b6ad
commit 800f063a1d

View File

@ -133,17 +133,32 @@ struct WCBTransfer
const char ** mimetypes; 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 struct WCBState
{ {
enum LG_ClipboardData stashedType;
char * stashedMimetype;
uint8_t * stashedContents;
ssize_t stashedSize;
bool isSelfCopy;
char lgMimetype[64]; char lgMimetype[64];
enum LG_ClipboardData pendingType;
char * pendingMimetype;
bool isSelfCopy;
enum LG_ClipboardData stashedType;
uint8_t * stashedContents;
ssize_t stashedSize;
bool haveRequest; bool haveRequest;
LG_ClipboardData type; LG_ClipboardData type;
struct ClipboardRead * currentRead;
}; };
static struct WaylandDSState wm; 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 // Since we can't copy or paste rich text, we should instead prefer actual
// images or plain text. // images or plain text.
if (type != LG_CLIPBOARD_DATA_NONE && if (type != LG_CLIPBOARD_DATA_NONE &&
(wcb.stashedType == LG_CLIPBOARD_DATA_NONE || (wcb.pendingType == LG_CLIPBOARD_DATA_NONE ||
strstr(wcb.stashedMimetype, "html"))) strstr(wcb.pendingMimetype, "html")))
{ {
wcb.stashedType = type; wcb.pendingType = type;
if (wcb.stashedMimetype) if (wcb.pendingMimetype)
free(wcb.stashedMimetype); free(wcb.pendingMimetype);
wcb.stashedMimetype = strdup(mimetype); wcb.pendingMimetype = strdup(mimetype);
} }
if (!strcmp(mimetype, wcb.lgMimetype)) if (!strcmp(mimetype, wcb.lgMimetype))
@ -1169,17 +1184,75 @@ static const struct wl_data_offer_listener dataOfferListener = {
static void dataDeviceHandleDataOffer(void * data, static void dataDeviceHandleDataOffer(void * data,
struct wl_data_device * dataDevice, struct wl_data_offer * offer) 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; wcb.isSelfCopy = false;
wl_data_offer_add_listener(offer, &dataOfferListener, NULL); 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) 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; return;
if (wcb.currentRead)
clipboardReadCancel(wcb.currentRead, true);
int fds[2]; int fds[2];
if (pipe(fds) < 0) if (pipe(fds) < 0)
{ {
@ -1187,10 +1260,10 @@ static void dataDeviceHandleSelection(void * data,
abort(); abort();
} }
wl_data_offer_receive(offer, wcb.stashedMimetype, fds[1]); wl_data_offer_receive(offer, wcb.pendingMimetype, fds[1]);
close(fds[1]); close(fds[1]);
free(wcb.stashedMimetype); free(wcb.pendingMimetype);
wcb.stashedMimetype = NULL; wcb.pendingMimetype = NULL;
wl_display_roundtrip(wm.display); wl_display_roundtrip(wm.display);
@ -1200,43 +1273,38 @@ static void dataDeviceHandleSelection(void * data,
wcb.stashedContents = NULL; wcb.stashedContents = NULL;
} }
size_t size = 4096, numRead = 0; struct ClipboardRead * data = malloc(sizeof(struct ClipboardRead));
uint8_t * buf = (uint8_t *) malloc(size); if (!data)
while (true)
{ {
ssize_t result = read(fds[0], buf + numRead, size - numRead); DEBUG_ERROR("Failed to allocate memory to read clipboard");
if (result < 0) close(fds[0]);
{ return;
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;
}
} }
wcb.stashedSize = numRead; data->fd = fds[0];
wcb.stashedContents = buf; data->size = 4096;
data->numRead = 0;
data->buf = malloc(data->size);
data->offer = offer;
data->type = wcb.pendingType;
close(fds[0]); if (!data->buf)
wl_data_offer_destroy(offer); {
app_clipboardNotify(wcb.stashedType, 0); 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 = { static const struct wl_data_device_listener dataDeviceListener = {