mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 15:11:31 +00:00
[client] clipboard: add seamless file backends
This commit is contained in:
@@ -30,6 +30,7 @@
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "../../src/clipboard.h"
|
||||
#include "../../src/clipboard_files.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/KVMFRClipboard.h"
|
||||
|
||||
@@ -75,6 +76,14 @@ static const char * jpegMimetypes[] =
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const char * fileMimetypes[] =
|
||||
{
|
||||
"x-special/gnome-copied-files",
|
||||
"text/uri-list",
|
||||
"application/x-kde-cutselection",
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const char ** cbTypeToMimetypes(enum LG_ClipboardData type)
|
||||
{
|
||||
switch (type)
|
||||
@@ -89,6 +98,8 @@ static const char ** cbTypeToMimetypes(enum LG_ClipboardData type)
|
||||
return tiffMimetypes;
|
||||
case LG_CLIPBOARD_DATA_JPEG:
|
||||
return jpegMimetypes;
|
||||
case LG_CLIPBOARD_DATA_FILES:
|
||||
return fileMimetypes;
|
||||
default:
|
||||
DEBUG_ERROR("invalid clipboard type");
|
||||
abort();
|
||||
@@ -141,6 +152,10 @@ static bool isTextMimetype(const char * mimetype)
|
||||
|
||||
static enum LG_ClipboardData mimetypeToCbType(const char * mimetype)
|
||||
{
|
||||
if (!strcmp(mimetype, "x-special/gnome-copied-files") ||
|
||||
!strcmp(mimetype, "text/uri-list"))
|
||||
return LG_CLIPBOARD_DATA_FILES;
|
||||
|
||||
if (isTextMimetype(mimetype))
|
||||
return LG_CLIPBOARD_DATA_TEXT;
|
||||
|
||||
@@ -164,6 +179,7 @@ static bool isImageCbtype(enum LG_ClipboardData type)
|
||||
switch (type)
|
||||
{
|
||||
case LG_CLIPBOARD_DATA_TEXT:
|
||||
case LG_CLIPBOARD_DATA_FILES:
|
||||
return false;
|
||||
case LG_CLIPBOARD_DATA_PNG:
|
||||
case LG_CLIPBOARD_DATA_BMP:
|
||||
@@ -244,6 +260,146 @@ static void clipboardReadCleanup(void * opaque)
|
||||
clipboardReadRelease(data);
|
||||
}
|
||||
|
||||
struct ClipboardFileImport
|
||||
{
|
||||
int fd;
|
||||
struct wl_data_offer * offer;
|
||||
char * mime;
|
||||
uint8_t * data;
|
||||
size_t size;
|
||||
size_t capacity;
|
||||
};
|
||||
|
||||
static bool fileImportGrow(struct ClipboardFileImport * import,
|
||||
size_t wanted)
|
||||
{
|
||||
if (wanted <= import->capacity)
|
||||
return true;
|
||||
size_t capacity = import->capacity ? import->capacity : 4096U;
|
||||
while (capacity < wanted)
|
||||
{
|
||||
if (capacity > SIZE_MAX / 2U)
|
||||
{
|
||||
capacity = wanted;
|
||||
break;
|
||||
}
|
||||
capacity *= 2U;
|
||||
}
|
||||
uint8_t * data = realloc(import->data, capacity);
|
||||
if (!data)
|
||||
return false;
|
||||
import->data = data;
|
||||
import->capacity = capacity;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void fileImportCleanup(void * opaque)
|
||||
{
|
||||
struct ClipboardFileImport * import = opaque;
|
||||
close(import->fd);
|
||||
free(import->mime);
|
||||
free(import->data);
|
||||
free(import);
|
||||
}
|
||||
|
||||
static void fileImportCallback(uint32_t events, void * opaque)
|
||||
{
|
||||
struct ClipboardFileImport * import = opaque;
|
||||
bool complete = false;
|
||||
bool failed = (events & EPOLLERR) != 0;
|
||||
while (!failed)
|
||||
{
|
||||
if (import->size > SIZE_MAX - 4096U)
|
||||
break;
|
||||
if (!fileImportGrow(import, import->size + 4096U))
|
||||
break;
|
||||
const ssize_t bytes = read(import->fd,
|
||||
import->data + import->size, import->capacity - import->size);
|
||||
if (bytes > 0)
|
||||
{
|
||||
import->size += (size_t)bytes;
|
||||
continue;
|
||||
}
|
||||
if (!bytes)
|
||||
{
|
||||
complete = true;
|
||||
break;
|
||||
}
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK)
|
||||
return;
|
||||
failed = true;
|
||||
}
|
||||
|
||||
LG_LOCK(wlCb.lock);
|
||||
const bool current = wlCb.fileImport == import &&
|
||||
wlCb.offer == import->offer;
|
||||
if (wlCb.fileImport == import)
|
||||
wlCb.fileImport = NULL;
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
if (current && (!complete ||
|
||||
!lgClipboardFiles_setLocal(import->mime,
|
||||
import->data, import->size)))
|
||||
{
|
||||
lgClipboardFiles_clearLocal();
|
||||
lgClipboard_release();
|
||||
}
|
||||
if (current)
|
||||
waylandPollUnregister(import->fd);
|
||||
}
|
||||
|
||||
static bool fileImportStart(struct wl_data_offer * offer, const char * mime)
|
||||
{
|
||||
int fds[2];
|
||||
if (pipe(fds) < 0)
|
||||
return false;
|
||||
const int flags = fcntl(fds[0], F_GETFL);
|
||||
const int readFdFlags = fcntl(fds[0], F_GETFD);
|
||||
const int writeFdFlags = fcntl(fds[1], F_GETFD);
|
||||
struct ClipboardFileImport * import = calloc(1, sizeof(*import));
|
||||
if (!import || flags < 0 || readFdFlags < 0 || writeFdFlags < 0 ||
|
||||
fcntl(fds[0], F_SETFL, flags | O_NONBLOCK) < 0 ||
|
||||
fcntl(fds[0], F_SETFD, readFdFlags | FD_CLOEXEC) < 0 ||
|
||||
fcntl(fds[1], F_SETFD, writeFdFlags | FD_CLOEXEC) < 0)
|
||||
{
|
||||
free(import);
|
||||
close(fds[0]);
|
||||
close(fds[1]);
|
||||
return false;
|
||||
}
|
||||
import->fd = fds[0];
|
||||
import->offer = offer;
|
||||
import->mime = strdup(mime);
|
||||
if (!import->mime)
|
||||
{
|
||||
fileImportCleanup(import);
|
||||
close(fds[1]);
|
||||
return false;
|
||||
}
|
||||
wl_data_offer_receive(offer, mime, fds[1]);
|
||||
close(fds[1]);
|
||||
|
||||
LG_LOCK(wlCb.lock);
|
||||
const bool installed = wlCb.offer == offer;
|
||||
struct ClipboardFileImport * old = installed ? wlCb.fileImport : NULL;
|
||||
bool registered = false;
|
||||
if (installed)
|
||||
{
|
||||
wlCb.fileImport = import;
|
||||
registered = waylandPollRegisterWithCleanup(import->fd,
|
||||
fileImportCallback, import, fileImportCleanup, EPOLLIN);
|
||||
if (!registered)
|
||||
wlCb.fileImport = old;
|
||||
}
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
if (registered && old)
|
||||
waylandPollUnregister(old->fd);
|
||||
if (!registered)
|
||||
fileImportCleanup(import);
|
||||
return registered;
|
||||
}
|
||||
|
||||
// Destination client handlers.
|
||||
|
||||
static void dataOfferHandleOffer(void * opaque, struct wl_data_offer * offer,
|
||||
@@ -276,7 +432,14 @@ static void dataOfferHandleOffer(void * opaque, struct wl_data_offer * offer,
|
||||
return;
|
||||
|
||||
if (data->mimetypes[type])
|
||||
return;
|
||||
{
|
||||
if (type != LG_CLIPBOARD_DATA_FILES ||
|
||||
strcmp(mimetype, "x-special/gnome-copied-files") ||
|
||||
!strcmp(data->mimetypes[type],
|
||||
"x-special/gnome-copied-files"))
|
||||
return;
|
||||
free(data->mimetypes[type]);
|
||||
}
|
||||
|
||||
data->mimetypes[type] = strdup(mimetype);
|
||||
}
|
||||
@@ -356,6 +519,8 @@ static void dataDeviceHandleSelection(void * opaque,
|
||||
char * oldMimetypes[LG_CLIPBOARD_DATA_NONE];
|
||||
LG_LOCK(wlCb.lock);
|
||||
struct ClipboardRead * read = clipboardReadTakeCurrentNL();
|
||||
struct ClipboardFileImport * oldImport = wlCb.fileImport;
|
||||
wlCb.fileImport = NULL;
|
||||
struct wl_data_offer * oldOffer = wlCb.offer;
|
||||
memcpy(oldMimetypes, wlCb.mimetypes, sizeof(oldMimetypes));
|
||||
wlCb.selectionSource = NULL;
|
||||
@@ -373,12 +538,22 @@ static void dataDeviceHandleSelection(void * opaque,
|
||||
clipboardReadRetire(read);
|
||||
lgClipboard_abort(request);
|
||||
}
|
||||
if (oldImport)
|
||||
waylandPollUnregister(oldImport->fd);
|
||||
if (oldOffer && oldOffer != offer)
|
||||
wl_data_offer_destroy(oldOffer);
|
||||
for (enum LG_ClipboardData i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
free(oldMimetypes[i]);
|
||||
|
||||
lgClipboard_notifyTypes(types, idx);
|
||||
if (wlCb.mimetypes[LG_CLIPBOARD_DATA_FILES])
|
||||
{
|
||||
if (!fileImportStart(offer, wlCb.mimetypes[LG_CLIPBOARD_DATA_FILES]))
|
||||
{
|
||||
lgClipboardFiles_clearLocal();
|
||||
lgClipboard_release();
|
||||
}
|
||||
}
|
||||
else
|
||||
lgClipboard_notifyTypes(types, idx);
|
||||
}
|
||||
|
||||
static void dataDeviceHandleEnter(void * data, struct wl_data_device * device,
|
||||
@@ -671,11 +846,13 @@ static void waylandCBInvalidateLocal(enum ClipboardInvalidateMode mode)
|
||||
return;
|
||||
}
|
||||
struct ClipboardRead * read = clipboardReadTakeCurrentNL();
|
||||
struct ClipboardFileImport * import = wlCb.fileImport;
|
||||
wlCb.fileImport = NULL;
|
||||
struct wl_data_offer * offer = wlCb.offer;
|
||||
wlCb.offer = NULL;
|
||||
memcpy(mimetypes, wlCb.mimetypes, sizeof(mimetypes));
|
||||
memset(wlCb.mimetypes, 0, sizeof(wlCb.mimetypes));
|
||||
const bool hadLocal = read || offer || hasAnyMimetype(mimetypes);
|
||||
const bool hadLocal = read || import || offer || hasAnyMimetype(mimetypes);
|
||||
const bool notifyProvider = mode == CLIPBOARD_INVALIDATE_FORCE ||
|
||||
(mode == CLIPBOARD_INVALIDATE_CONDITIONAL && hadLocal);
|
||||
const LG_ClipboardRequest request = read ? read->request :
|
||||
@@ -694,6 +871,10 @@ static void waylandCBInvalidateLocal(enum ClipboardInvalidateMode mode)
|
||||
}
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
|
||||
if (import)
|
||||
waylandPollUnregister(import->fd);
|
||||
lgClipboardFiles_clearLocal();
|
||||
|
||||
if (read && !notifyProvider)
|
||||
lgClipboard_abort(request);
|
||||
|
||||
@@ -997,6 +1178,138 @@ static const LG_ClipboardStreamOps clipboardWriteStream =
|
||||
.cancel = clipboardWriteCancel,
|
||||
};
|
||||
|
||||
struct ClipboardFileWrite
|
||||
{
|
||||
int fd;
|
||||
uint8_t * data;
|
||||
size_t size;
|
||||
size_t offset;
|
||||
struct WCBTransfer * transfer;
|
||||
bool deliversUri;
|
||||
bool complete;
|
||||
};
|
||||
|
||||
static void fileTransferDestroy(struct WCBTransfer * transfer)
|
||||
{
|
||||
if (transfer->filePresentation)
|
||||
lgClipboardFiles_remotePresentationRelease(transfer->filePresentation);
|
||||
free(transfer->fileUri);
|
||||
free(transfer->fileGnome);
|
||||
free(transfer->fileKde);
|
||||
free(transfer);
|
||||
}
|
||||
|
||||
static void fileTransferRetain(struct WCBTransfer * transfer)
|
||||
{
|
||||
atomic_fetch_add_explicit(
|
||||
&transfer->references, 1, memory_order_relaxed);
|
||||
}
|
||||
|
||||
static void fileTransferRelease(struct WCBTransfer * transfer)
|
||||
{
|
||||
if (atomic_fetch_sub_explicit(
|
||||
&transfer->references, 1, memory_order_acq_rel) == 1)
|
||||
fileTransferDestroy(transfer);
|
||||
}
|
||||
|
||||
static void clipboardFileWriteCleanup(void * opaque)
|
||||
{
|
||||
struct ClipboardFileWrite * write = opaque;
|
||||
if (write->complete && write->deliversUri)
|
||||
lgClipboardFiles_remotePresentationDelivered(
|
||||
write->transfer->filePresentation);
|
||||
close(write->fd);
|
||||
free(write->data);
|
||||
fileTransferRelease(write->transfer);
|
||||
free(write);
|
||||
}
|
||||
|
||||
static void clipboardFileWriteCallback(uint32_t events, void * opaque)
|
||||
{
|
||||
struct ClipboardFileWrite * output = opaque;
|
||||
if (events & (EPOLLERR | EPOLLHUP))
|
||||
{
|
||||
waylandPollUnregister(output->fd);
|
||||
return;
|
||||
}
|
||||
while (output->offset < output->size)
|
||||
{
|
||||
const ssize_t bytes = write(output->fd,
|
||||
output->data + output->offset, output->size - output->offset);
|
||||
if (bytes > 0)
|
||||
{
|
||||
output->offset += (size_t)bytes;
|
||||
continue;
|
||||
}
|
||||
if (bytes < 0 && errno == EINTR)
|
||||
continue;
|
||||
if (bytes < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
|
||||
return;
|
||||
waylandPollUnregister(output->fd);
|
||||
return;
|
||||
}
|
||||
output->complete = true;
|
||||
waylandPollUnregister(output->fd);
|
||||
}
|
||||
|
||||
static bool clipboardFileWriteStart(int fd, const void * data, size_t size,
|
||||
struct WCBTransfer * transfer, bool deliversUri)
|
||||
{
|
||||
struct ClipboardFileWrite * output = calloc(1, sizeof(*output));
|
||||
if (!output)
|
||||
return false;
|
||||
output->data = size ? malloc(size) : NULL;
|
||||
if (size && !output->data)
|
||||
{
|
||||
free(output);
|
||||
return false;
|
||||
}
|
||||
if (size)
|
||||
memcpy(output->data, data, size);
|
||||
output->fd = fd;
|
||||
output->size = size;
|
||||
output->transfer = transfer;
|
||||
output->deliversUri = deliversUri;
|
||||
fileTransferRetain(transfer);
|
||||
const int flags = fcntl(fd, F_GETFL);
|
||||
const int fdFlags = fcntl(fd, F_GETFD);
|
||||
if (flags < 0 || fdFlags < 0 ||
|
||||
fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0 ||
|
||||
fcntl(fd, F_SETFD, fdFlags | FD_CLOEXEC) < 0 ||
|
||||
!waylandPollRegisterWithCleanup(fd, clipboardFileWriteCallback,
|
||||
output, clipboardFileWriteCleanup, EPOLLOUT))
|
||||
{
|
||||
free(output->data);
|
||||
fileTransferRelease(output->transfer);
|
||||
free(output);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool fileTransferPayload(const struct WCBTransfer * transfer,
|
||||
const char * mime, const char ** data, size_t * size)
|
||||
{
|
||||
if (!strcmp(mime, "text/uri-list"))
|
||||
{
|
||||
*data = transfer->fileUri;
|
||||
*size = transfer->fileUriSize;
|
||||
}
|
||||
else if (!strcmp(mime, "x-special/gnome-copied-files"))
|
||||
{
|
||||
*data = transfer->fileGnome;
|
||||
*size = transfer->fileGnomeSize;
|
||||
}
|
||||
else if (!strcmp(mime, "application/x-kde-cutselection"))
|
||||
{
|
||||
*data = transfer->fileKde;
|
||||
*size = transfer->fileKdeSize;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
return *data != NULL;
|
||||
}
|
||||
|
||||
static void clipboardWriteCleanup(void * opaque)
|
||||
{
|
||||
struct ClipboardWrite * data = opaque;
|
||||
@@ -1092,6 +1405,19 @@ static void dataSourceHandleSend(void * data, struct wl_data_source * source,
|
||||
const char * mimetype, int fd)
|
||||
{
|
||||
struct WCBTransfer * transfer = (struct WCBTransfer *) data;
|
||||
if (transfer->type == LG_CLIPBOARD_DATA_FILES)
|
||||
{
|
||||
const char * payload;
|
||||
size_t size;
|
||||
const bool deliversUri = !strcmp(mimetype, "text/uri-list") ||
|
||||
!strcmp(mimetype, "x-special/gnome-copied-files");
|
||||
if (fileTransferPayload(transfer, mimetype, &payload, &size) &&
|
||||
clipboardFileWriteStart(fd, payload, size,
|
||||
transfer, deliversUri))
|
||||
return;
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
if (containsMimetype(transfer->mimetypes, mimetype))
|
||||
{
|
||||
struct ClipboardWrite * data = calloc(1, sizeof(*data));
|
||||
@@ -1178,7 +1504,7 @@ static void dataSourceHandleCancelled(void * data,
|
||||
wlCb.selectionSource = NULL;
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
|
||||
free(transfer);
|
||||
fileTransferRelease(transfer);
|
||||
wl_data_source_destroy(source);
|
||||
}
|
||||
|
||||
@@ -1188,34 +1514,53 @@ static const struct wl_data_source_listener dataSourceListener = {
|
||||
.cancelled = dataSourceHandleCancelled,
|
||||
};
|
||||
|
||||
static void waylandCBPublish(LG_ClipboardData type)
|
||||
static bool waylandCBPublish(LG_ClipboardData type)
|
||||
{
|
||||
struct WCBTransfer * transfer = malloc(sizeof(*transfer));
|
||||
struct WCBTransfer * transfer = calloc(1, sizeof(*transfer));
|
||||
if (!transfer)
|
||||
{
|
||||
DEBUG_ERROR("Out of memory when allocating WCBTransfer");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
atomic_init(&transfer->references, 1);
|
||||
|
||||
transfer->mimetypes = cbTypeToMimetypes(type);
|
||||
transfer->type = type;
|
||||
transfer->next = NULL;
|
||||
if (type == LG_CLIPBOARD_DATA_FILES)
|
||||
transfer->filePresentation =
|
||||
lgClipboardFiles_remotePresentationAcquire();
|
||||
if (type == LG_CLIPBOARD_DATA_FILES &&
|
||||
(!transfer->filePresentation ||
|
||||
!lgClipboardFiles_getRemotePresentation(transfer->filePresentation,
|
||||
"text/uri-list",
|
||||
&transfer->fileUri, &transfer->fileUriSize) ||
|
||||
!lgClipboardFiles_getRemotePresentation(transfer->filePresentation,
|
||||
"x-special/gnome-copied-files",
|
||||
&transfer->fileGnome, &transfer->fileGnomeSize) ||
|
||||
!lgClipboardFiles_getRemotePresentation(transfer->filePresentation,
|
||||
"application/x-kde-cutselection",
|
||||
&transfer->fileKde, &transfer->fileKdeSize)))
|
||||
{
|
||||
fileTransferRelease(transfer);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct wl_data_source * source =
|
||||
wl_data_device_manager_create_data_source(wlWm.dataDeviceManager);
|
||||
if (!source)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create clipboard data source");
|
||||
free(transfer);
|
||||
return;
|
||||
fileTransferRelease(transfer);
|
||||
return false;
|
||||
}
|
||||
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;
|
||||
fileTransferRelease(transfer);
|
||||
return false;
|
||||
}
|
||||
for (const char ** mimetype = transfer->mimetypes; *mimetype; mimetype++)
|
||||
wl_data_source_offer(source, *mimetype);
|
||||
@@ -1230,17 +1575,19 @@ static void waylandCBPublish(LG_ClipboardData type)
|
||||
wl_data_device_set_selection(wlCb.dataDevice, source,
|
||||
wlWm.keyboardEnterSerial);
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
|
||||
wl_data_source_destroy(source);
|
||||
free(transfer);
|
||||
fileTransferRelease(transfer);
|
||||
return false;
|
||||
}
|
||||
|
||||
void waylandCBNotice(LG_ClipboardData type)
|
||||
{
|
||||
waylandCBPublish(type);
|
||||
if (!waylandCBPublish(type))
|
||||
waylandCBRelease();
|
||||
}
|
||||
|
||||
void waylandCBRelease(void)
|
||||
@@ -1260,6 +1607,7 @@ void waylandCBFree(void)
|
||||
char * mimetypes[LG_CLIPBOARD_DATA_NONE];
|
||||
LG_LOCK(wlCb.lock);
|
||||
struct ClipboardRead * read = clipboardReadTakeCurrentNL();
|
||||
struct ClipboardFileImport * import = wlCb.fileImport;
|
||||
struct wl_data_offer * offer = wlCb.offer;
|
||||
struct wl_data_offer * dndOffer = wlCb.dndOffer;
|
||||
struct wl_data_device * dataDevice = wlCb.dataDevice;
|
||||
@@ -1267,16 +1615,20 @@ void waylandCBFree(void)
|
||||
if (dataDevice && wlCb.selectionSource)
|
||||
wl_data_device_set_selection(dataDevice, NULL,
|
||||
wlWm.keyboardEnterSerial);
|
||||
wlCb.offer = NULL;
|
||||
wlCb.dndOffer = NULL;
|
||||
wlCb.dataDevice = NULL;
|
||||
wlCb.offer = NULL;
|
||||
wlCb.dndOffer = NULL;
|
||||
wlCb.dataDevice = NULL;
|
||||
wlCb.selectionSource = NULL;
|
||||
wlCb.sources = NULL;
|
||||
wlCb.fileImport = NULL;
|
||||
memcpy(mimetypes, wlCb.mimetypes, sizeof(mimetypes));
|
||||
memset(wlCb.mimetypes, 0, sizeof(wlCb.mimetypes));
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
|
||||
clipboardReadRetire(read);
|
||||
if (import)
|
||||
waylandPollUnregister(import->fd);
|
||||
lgClipboardFiles_clearLocal();
|
||||
if (offer)
|
||||
wl_data_offer_destroy(offer);
|
||||
if (dndOffer && dndOffer != offer)
|
||||
@@ -1287,7 +1639,7 @@ void waylandCBFree(void)
|
||||
{
|
||||
struct WCBTransfer * next = sources->next;
|
||||
wl_data_source_destroy(sources->source);
|
||||
free(sources);
|
||||
fileTransferRelease(sources);
|
||||
sources = next;
|
||||
}
|
||||
for (enum LG_ClipboardData i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
|
||||
@@ -287,12 +287,22 @@ struct WaylandDSState
|
||||
|
||||
struct WCBTransfer
|
||||
{
|
||||
LG_ClipboardData type;
|
||||
atomic_uint references;
|
||||
LG_ClipboardData type;
|
||||
const char ** mimetypes;
|
||||
struct wl_data_source * source;
|
||||
struct WCBTransfer * next;
|
||||
uint64_t filePresentation;
|
||||
char * fileUri;
|
||||
size_t fileUriSize;
|
||||
char * fileGnome;
|
||||
size_t fileGnomeSize;
|
||||
char * fileKde;
|
||||
size_t fileKdeSize;
|
||||
};
|
||||
|
||||
struct ClipboardFileImport;
|
||||
|
||||
struct ClipboardRead
|
||||
{
|
||||
atomic_uint references;
|
||||
@@ -311,18 +321,19 @@ struct ClipboardRead
|
||||
|
||||
struct WCBState
|
||||
{
|
||||
struct wl_data_device * dataDevice;
|
||||
char lgMimetype[64];
|
||||
struct wl_data_device * dataDevice;
|
||||
char lgMimetype[64];
|
||||
|
||||
char * mimetypes[LG_CLIPBOARD_DATA_NONE];
|
||||
struct wl_data_offer * offer;
|
||||
struct wl_data_offer * dndOffer;
|
||||
char * mimetypes[LG_CLIPBOARD_DATA_NONE];
|
||||
struct wl_data_offer * offer;
|
||||
struct wl_data_offer * dndOffer;
|
||||
|
||||
struct wl_data_source * selectionSource;
|
||||
struct WCBTransfer * sources;
|
||||
struct wl_data_source * selectionSource;
|
||||
struct WCBTransfer * sources;
|
||||
|
||||
LG_Lock lock;
|
||||
struct ClipboardRead * currentRead;
|
||||
LG_Lock lock;
|
||||
struct ClipboardRead * currentRead;
|
||||
struct ClipboardFileImport * fileImport;
|
||||
};
|
||||
|
||||
extern struct WaylandDSState wlWm;
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <X11/Xatom.h>
|
||||
|
||||
#include "../../src/clipboard.h"
|
||||
#include "../../src/clipboard_files.h"
|
||||
#include "common/array.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/KVMFRClipboard.h"
|
||||
@@ -67,19 +68,39 @@ struct X11ClipboardWrite
|
||||
bool ready;
|
||||
bool endPending;
|
||||
bool creating;
|
||||
bool file;
|
||||
uint8_t * fileData;
|
||||
size_t fileSize;
|
||||
uint64_t filePresentation;
|
||||
};
|
||||
|
||||
struct X11ClipboardFileImport
|
||||
{
|
||||
Window window;
|
||||
Atom target;
|
||||
const char * mime;
|
||||
long propertyOffset;
|
||||
uint8_t * data;
|
||||
size_t size;
|
||||
size_t capacity;
|
||||
bool incremental;
|
||||
};
|
||||
|
||||
struct X11ClipboardState
|
||||
{
|
||||
LG_Lock lock;
|
||||
Atom aCurSelection;
|
||||
Atom aTypes[LG_CLIPBOARD_DATA_NONE];
|
||||
Window targetsWindow;
|
||||
LG_ClipboardData type;
|
||||
bool haveRequest;
|
||||
LG_Lock lock;
|
||||
Atom aCurSelection;
|
||||
Atom aTypes[LG_CLIPBOARD_DATA_NONE];
|
||||
Atom aFileGnome;
|
||||
Atom aFileKde;
|
||||
Window targetsWindow;
|
||||
LG_ClipboardData type;
|
||||
bool haveRequest;
|
||||
uint64_t filePresentation;
|
||||
|
||||
struct X11ClipboardRead read;
|
||||
struct X11ClipboardWrite * writes;
|
||||
struct X11ClipboardRead read;
|
||||
struct X11ClipboardWrite * writes;
|
||||
struct X11ClipboardFileImport fileImport;
|
||||
};
|
||||
|
||||
static const char * atomTypes[] =
|
||||
@@ -88,9 +109,14 @@ static const char * atomTypes[] =
|
||||
"image/png",
|
||||
"image/bmp",
|
||||
"image/tiff",
|
||||
"image/jpeg"
|
||||
"image/jpeg",
|
||||
"text/uri-list",
|
||||
};
|
||||
|
||||
static const char fileGnomeMime[] = "x-special/gnome-copied-files";
|
||||
static const char fileKdeMime[] = "application/x-kde-cutselection";
|
||||
static const char fileUriMime[] = "text/uri-list";
|
||||
|
||||
static struct X11ClipboardState x11cb;
|
||||
|
||||
// forwards
|
||||
@@ -104,6 +130,23 @@ static bool writeLinkedNL(const struct X11ClipboardWrite * write);
|
||||
static void writeRemoveNL(struct X11ClipboardWrite * write);
|
||||
static bool advanceReadPropertyNL(unsigned long units);
|
||||
static void clearTargetsNL(void);
|
||||
static void clearFileImportNL(void);
|
||||
static struct X11ClipboardWrite * takeFileWritesNL(void);
|
||||
static struct X11ClipboardWrite * takeFileWritesForWindowNL(Window window);
|
||||
static void cancelFileWrites(
|
||||
struct X11ClipboardWrite * writes, bool terminate);
|
||||
static void x11CBFileImportIncr(const XPropertyEvent e);
|
||||
|
||||
static const char * fileMimeForAtom(Atom atom)
|
||||
{
|
||||
if (atom == x11cb.aTypes[LG_CLIPBOARD_DATA_FILES])
|
||||
return fileUriMime;
|
||||
if (atom == x11cb.aFileGnome)
|
||||
return fileGnomeMime;
|
||||
if (atom == x11cb.aFileKde)
|
||||
return fileKdeMime;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool x11CBEventThread(const XEvent * xe)
|
||||
{
|
||||
@@ -121,11 +164,30 @@ bool x11CBEventThread(const XEvent * xe)
|
||||
x11CBSelectionNotify(xe->xselection);
|
||||
return true;
|
||||
|
||||
case DestroyNotify:
|
||||
{
|
||||
LG_LOCK(x11cb.lock);
|
||||
struct X11ClipboardWrite * writes =
|
||||
takeFileWritesForWindowNL(xe->xdestroywindow.window);
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
if (!writes)
|
||||
return false;
|
||||
cancelFileWrites(writes, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
case PropertyNotify:
|
||||
if (xe->xproperty.state == PropertyNewValue &&
|
||||
xe->xproperty.atom == x11atoms.SEL_DATA)
|
||||
{
|
||||
x11CBSelectionIncr(xe->xproperty);
|
||||
LG_LOCK(x11cb.lock);
|
||||
const bool fileImport = x11cb.fileImport.window &&
|
||||
xe->xproperty.window == x11cb.fileImport.window;
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
if (fileImport)
|
||||
x11CBFileImportIncr(xe->xproperty);
|
||||
else
|
||||
x11CBSelectionIncr(xe->xproperty);
|
||||
return true;
|
||||
}
|
||||
if (xe->xproperty.state == PropertyDelete)
|
||||
@@ -134,12 +196,47 @@ bool x11CBEventThread(const XEvent * xe)
|
||||
LG_LOCK(x11cb.lock);
|
||||
struct X11ClipboardWrite * write;
|
||||
for (write = x11cb.writes; write; write = write->next)
|
||||
if (write->request != LG_CLIPBOARD_REQUEST_INVALID &&
|
||||
xe->xproperty.window == write->event.xselection.requestor &&
|
||||
if (xe->xproperty.window == write->event.xselection.requestor &&
|
||||
xe->xproperty.atom == write->event.xselection.property)
|
||||
break;
|
||||
if (write)
|
||||
{
|
||||
if (write->file)
|
||||
{
|
||||
const size_t remaining = write->fileSize - (size_t)write->offset;
|
||||
if (remaining)
|
||||
{
|
||||
const size_t chunk = remaining > KVMFR_CLIPBOARD_DATA_BYTES ?
|
||||
KVMFR_CLIPBOARD_DATA_BYTES : remaining;
|
||||
XChangeProperty(x11.display,
|
||||
write->event.xselection.requestor,
|
||||
write->event.xselection.property,
|
||||
write->event.xselection.target, 8, PropModeReplace,
|
||||
write->fileData + (size_t)write->offset, (int)chunk);
|
||||
write->offset += chunk;
|
||||
XFlush(x11.display);
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
return true;
|
||||
}
|
||||
|
||||
XChangeProperty(x11.display,
|
||||
write->event.xselection.requestor,
|
||||
write->event.xselection.property,
|
||||
write->event.xselection.target, 8, PropModeReplace,
|
||||
NULL, 0);
|
||||
XFlush(x11.display);
|
||||
if (write->event.xselection.target != x11cb.aFileKde)
|
||||
lgClipboardFiles_remotePresentationDelivered(
|
||||
write->filePresentation);
|
||||
const uint64_t presentation = write->filePresentation;
|
||||
writeRemoveNL(write);
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
lgClipboardFiles_remotePresentationRelease(presentation);
|
||||
free(write->fileData);
|
||||
free(write);
|
||||
return true;
|
||||
}
|
||||
|
||||
write->ready = true;
|
||||
if (write->blocked)
|
||||
{
|
||||
@@ -186,6 +283,15 @@ bool x11CBInit(void)
|
||||
}
|
||||
}
|
||||
|
||||
x11cb.aFileGnome = XInternAtom(x11.display, fileGnomeMime, False);
|
||||
x11cb.aFileKde = XInternAtom(x11.display, fileKdeMime, False);
|
||||
if (x11cb.aFileGnome == BadAlloc || x11cb.aFileGnome == BadValue ||
|
||||
x11cb.aFileKde == BadAlloc || x11cb.aFileKde == BadValue)
|
||||
{
|
||||
DEBUG_ERROR("failed to get clipboard file atoms");
|
||||
return false;
|
||||
}
|
||||
|
||||
// use xfixes to get clipboard change notifications
|
||||
if (!XFixesQueryExtension(x11.display, &x11.eventBase, &x11.errorBase))
|
||||
{
|
||||
@@ -372,9 +478,15 @@ static void x11CBSelectionRequest(const XSelectionRequestEvent e)
|
||||
// target list requested
|
||||
if (e.target == x11atoms.TARGETS)
|
||||
{
|
||||
Atom targets[2];
|
||||
Atom targets[4];
|
||||
targets[0] = x11atoms.TARGETS;
|
||||
targets[1] = x11cb.aTypes[requestType];
|
||||
int count = 2;
|
||||
if (requestType == LG_CLIPBOARD_DATA_FILES)
|
||||
{
|
||||
targets[count++] = x11cb.aFileGnome;
|
||||
targets[count++] = x11cb.aFileKde;
|
||||
}
|
||||
|
||||
XChangeProperty(
|
||||
e.display,
|
||||
@@ -384,12 +496,98 @@ static void x11CBSelectionRequest(const XSelectionRequestEvent e)
|
||||
32,
|
||||
PropModeReplace,
|
||||
(unsigned char*)targets,
|
||||
ARRAY_LENGTH(targets)
|
||||
count
|
||||
);
|
||||
|
||||
goto send;
|
||||
}
|
||||
|
||||
const char * fileMime = fileMimeForAtom(e.target);
|
||||
if (requestType == LG_CLIPBOARD_DATA_FILES && fileMime)
|
||||
{
|
||||
char * data = NULL;
|
||||
size_t size = 0;
|
||||
uint64_t writePresentation = 0;
|
||||
LG_LOCK(x11cb.lock);
|
||||
const uint64_t presentation = x11cb.filePresentation;
|
||||
bool available = x11cb.haveRequest &&
|
||||
x11cb.type == LG_CLIPBOARD_DATA_FILES && presentation;
|
||||
if (available)
|
||||
{
|
||||
writePresentation =
|
||||
lgClipboardFiles_remotePresentationAcquire();
|
||||
available = writePresentation == presentation &&
|
||||
lgClipboardFiles_getRemotePresentation(writePresentation,
|
||||
fileMime, &data, &size);
|
||||
}
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
if (!available)
|
||||
{
|
||||
free(data);
|
||||
if (writePresentation)
|
||||
lgClipboardFiles_remotePresentationRelease(writePresentation);
|
||||
goto nodata;
|
||||
}
|
||||
|
||||
struct X11ClipboardWrite * write = calloc(1, sizeof(*write));
|
||||
if (!write)
|
||||
{
|
||||
free(data);
|
||||
lgClipboardFiles_remotePresentationRelease(writePresentation);
|
||||
DEBUG_ERROR("out of memory");
|
||||
goto nodata;
|
||||
}
|
||||
write->event = *s;
|
||||
write->request = LG_CLIPBOARD_REQUEST_INVALID;
|
||||
write->type = LG_CLIPBOARD_DATA_FILES;
|
||||
write->file = true;
|
||||
write->fileData = (uint8_t *)data;
|
||||
write->fileSize = size;
|
||||
write->filePresentation = writePresentation;
|
||||
write->begun = true;
|
||||
|
||||
LG_LOCK(x11cb.lock);
|
||||
bool duplicate = false;
|
||||
for (struct X11ClipboardWrite * current = x11cb.writes;
|
||||
current; current = current->next)
|
||||
{
|
||||
duplicate = current->event.xselection.requestor == e.requestor &&
|
||||
current->event.xselection.property == e.property;
|
||||
if (duplicate)
|
||||
break;
|
||||
}
|
||||
if (!duplicate && x11cb.haveRequest &&
|
||||
x11cb.type == LG_CLIPBOARD_DATA_FILES &&
|
||||
x11cb.filePresentation == presentation)
|
||||
{
|
||||
write->next = x11cb.writes;
|
||||
x11cb.writes = write;
|
||||
const unsigned long hint = size > UINT32_MAX ?
|
||||
UINT32_MAX : (unsigned long)size;
|
||||
XChangeProperty(x11.display, e.requestor, e.property,
|
||||
x11atoms.INCR, 32, PropModeReplace,
|
||||
(const unsigned char *)&hint, 1);
|
||||
XSelectInput(x11.display, e.requestor,
|
||||
PropertyChangeMask | StructureNotifyMask);
|
||||
}
|
||||
else
|
||||
duplicate = true;
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
|
||||
if (duplicate)
|
||||
{
|
||||
free(write->fileData);
|
||||
lgClipboardFiles_remotePresentationRelease(
|
||||
write->filePresentation);
|
||||
free(write);
|
||||
goto nodata;
|
||||
}
|
||||
|
||||
x11CBWriteSend(s);
|
||||
free(s);
|
||||
return;
|
||||
}
|
||||
|
||||
// look to see if we can satisfy the data type
|
||||
for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
if (x11cb.aTypes[i] == e.target && requestType == i)
|
||||
@@ -467,7 +665,22 @@ send:
|
||||
|
||||
static void x11CBSelectionClear(const XSelectionClearEvent e)
|
||||
{
|
||||
(void)e;
|
||||
if (e.selection != x11atoms.CLIPBOARD)
|
||||
return;
|
||||
|
||||
LG_LOCK(x11cb.lock);
|
||||
if (XGetSelectionOwner(x11.display, x11atoms.CLIPBOARD) == x11.window)
|
||||
{
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
return;
|
||||
}
|
||||
x11cb.haveRequest = false;
|
||||
const uint64_t presentation = x11cb.filePresentation;
|
||||
x11cb.filePresentation = 0;
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
|
||||
if (presentation)
|
||||
lgClipboardFiles_remotePresentationRelease(presentation);
|
||||
}
|
||||
|
||||
/* x11cb.lock must be held. */
|
||||
@@ -531,6 +744,298 @@ static void clearTargetsNL(void)
|
||||
x11cb.targetsWindow = 0;
|
||||
}
|
||||
|
||||
/* x11cb.lock must be held. */
|
||||
static struct X11ClipboardFileImport takeFileImportNL(void)
|
||||
{
|
||||
const struct X11ClipboardFileImport import = x11cb.fileImport;
|
||||
x11cb.fileImport = (struct X11ClipboardFileImport) { 0 };
|
||||
if (import.window)
|
||||
XDestroyWindow(x11.display, import.window);
|
||||
return import;
|
||||
}
|
||||
|
||||
/* x11cb.lock must be held. */
|
||||
static void clearFileImportNL(void)
|
||||
{
|
||||
const struct X11ClipboardFileImport import = takeFileImportNL();
|
||||
free(import.data);
|
||||
}
|
||||
|
||||
/* x11cb.lock must be held. */
|
||||
static struct X11ClipboardWrite * takeFileWritesNL(void)
|
||||
{
|
||||
struct X11ClipboardWrite * result = NULL;
|
||||
struct X11ClipboardWrite ** link = &x11cb.writes;
|
||||
while (*link)
|
||||
{
|
||||
struct X11ClipboardWrite * write = *link;
|
||||
if (!write->file)
|
||||
{
|
||||
link = &write->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
*link = write->next;
|
||||
write->next = result;
|
||||
result = write;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* x11cb.lock must be held. */
|
||||
static struct X11ClipboardWrite * takeFileWritesForWindowNL(Window window)
|
||||
{
|
||||
struct X11ClipboardWrite * result = NULL;
|
||||
struct X11ClipboardWrite ** link = &x11cb.writes;
|
||||
while (*link)
|
||||
{
|
||||
struct X11ClipboardWrite * write = *link;
|
||||
if (!write->file ||
|
||||
write->event.xselection.requestor != window)
|
||||
{
|
||||
link = &write->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
*link = write->next;
|
||||
write->next = result;
|
||||
result = write;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void cancelFileWrites(
|
||||
struct X11ClipboardWrite * writes, bool terminate)
|
||||
{
|
||||
const bool flush = terminate && writes;
|
||||
while (writes)
|
||||
{
|
||||
struct X11ClipboardWrite * next = writes->next;
|
||||
if (terminate)
|
||||
XChangeProperty(x11.display, writes->event.xselection.requestor,
|
||||
writes->event.xselection.property,
|
||||
writes->event.xselection.target, 8, PropModeReplace, NULL, 0);
|
||||
lgClipboardFiles_remotePresentationRelease(
|
||||
writes->filePresentation);
|
||||
free(writes->fileData);
|
||||
free(writes);
|
||||
writes = next;
|
||||
}
|
||||
if (flush)
|
||||
XFlush(x11.display);
|
||||
}
|
||||
|
||||
static bool fileImportGrowNL(size_t wanted)
|
||||
{
|
||||
if (wanted <= x11cb.fileImport.capacity)
|
||||
return true;
|
||||
size_t capacity = x11cb.fileImport.capacity ?
|
||||
x11cb.fileImport.capacity : 4096U;
|
||||
while (capacity < wanted)
|
||||
{
|
||||
if (capacity > SIZE_MAX / 2U)
|
||||
{
|
||||
capacity = wanted;
|
||||
break;
|
||||
}
|
||||
capacity *= 2U;
|
||||
}
|
||||
uint8_t * data = realloc(x11cb.fileImport.data, capacity);
|
||||
if (!data)
|
||||
return false;
|
||||
x11cb.fileImport.data = data;
|
||||
x11cb.fileImport.capacity = capacity;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool startFileImport(Atom target)
|
||||
{
|
||||
const char * mime = fileMimeForAtom(target);
|
||||
if (!mime)
|
||||
return false;
|
||||
|
||||
LG_LOCK(x11cb.lock);
|
||||
clearFileImportNL();
|
||||
if (x11cb.aCurSelection == BadValue)
|
||||
{
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
return false;
|
||||
}
|
||||
|
||||
const Window window = XCreateSimpleWindow(
|
||||
x11.display, x11.window, 0, 0, 1, 1, 0, 0, 0);
|
||||
if (!window)
|
||||
{
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
return false;
|
||||
}
|
||||
|
||||
x11cb.fileImport = (struct X11ClipboardFileImport)
|
||||
{
|
||||
.window = window,
|
||||
.target = target,
|
||||
.mime = mime,
|
||||
};
|
||||
XSelectInput(x11.display, window, PropertyChangeMask);
|
||||
XConvertSelection(x11.display, x11cb.aCurSelection, target,
|
||||
x11atoms.SEL_DATA, window, CurrentTime);
|
||||
XFlush(x11.display);
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void finishFileImport(struct X11ClipboardFileImport import,
|
||||
const void * data, size_t size)
|
||||
{
|
||||
if (!lgClipboardFiles_setLocal(import.mime, data, size))
|
||||
{
|
||||
lgClipboardFiles_clearLocal();
|
||||
lgClipboard_release();
|
||||
}
|
||||
free(import.data);
|
||||
}
|
||||
|
||||
static bool x11CBFileImportSelectionNotify(const XSelectionEvent e)
|
||||
{
|
||||
Atom type;
|
||||
int format;
|
||||
unsigned long itemCount;
|
||||
unsigned long after;
|
||||
unsigned char * data = NULL;
|
||||
|
||||
LG_LOCK(x11cb.lock);
|
||||
if (!x11cb.fileImport.window || e.requestor != x11cb.fileImport.window)
|
||||
{
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (e.target != x11cb.fileImport.target ||
|
||||
e.property != x11atoms.SEL_DATA ||
|
||||
XGetWindowProperty(e.display, e.requestor, e.property, 0, ~0L,
|
||||
True, AnyPropertyType, &type, &format, &itemCount, &after,
|
||||
&data) != Success)
|
||||
{
|
||||
const struct X11ClipboardFileImport failed = takeFileImportNL();
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
if (data)
|
||||
XFree(data);
|
||||
free(failed.data);
|
||||
lgClipboard_release();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type == x11atoms.INCR)
|
||||
{
|
||||
if (!data || format != 32 || itemCount < 1 || after)
|
||||
{
|
||||
const struct X11ClipboardFileImport failed = takeFileImportNL();
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
if (data)
|
||||
XFree(data);
|
||||
free(failed.data);
|
||||
lgClipboard_release();
|
||||
return true;
|
||||
}
|
||||
x11cb.fileImport.incremental = true;
|
||||
XFree(data);
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
return true;
|
||||
}
|
||||
|
||||
const bool valid = type == x11cb.fileImport.target && format == 8 &&
|
||||
!after && (!itemCount || data);
|
||||
const struct X11ClipboardFileImport import = takeFileImportNL();
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
if (valid)
|
||||
finishFileImport(import, data, itemCount);
|
||||
else
|
||||
{
|
||||
free(import.data);
|
||||
lgClipboard_release();
|
||||
}
|
||||
if (data)
|
||||
XFree(data);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void x11CBFileImportIncr(const XPropertyEvent e)
|
||||
{
|
||||
Atom type;
|
||||
int format;
|
||||
unsigned long itemCount;
|
||||
unsigned long after;
|
||||
unsigned char * data = NULL;
|
||||
|
||||
LG_LOCK(x11cb.lock);
|
||||
if (!x11cb.fileImport.window || !x11cb.fileImport.incremental ||
|
||||
e.window != x11cb.fileImport.window || e.atom != x11atoms.SEL_DATA)
|
||||
{
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
return;
|
||||
}
|
||||
|
||||
readProperty:
|
||||
if (XGetWindowProperty(e.display, e.window, e.atom,
|
||||
x11cb.fileImport.propertyOffset,
|
||||
(KVMFR_CLIPBOARD_DATA_BYTES + 3U) / 4U,
|
||||
True, AnyPropertyType, &type, &format, &itemCount, &after,
|
||||
&data) != Success || (itemCount && !data) || (!itemCount && after) ||
|
||||
type != x11cb.fileImport.target || format != 8 ||
|
||||
itemCount > SIZE_MAX ||
|
||||
(size_t)itemCount > SIZE_MAX - x11cb.fileImport.size ||
|
||||
!fileImportGrowNL(x11cb.fileImport.size + (size_t)itemCount))
|
||||
{
|
||||
const struct X11ClipboardFileImport failed = takeFileImportNL();
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
if (data)
|
||||
XFree(data);
|
||||
free(failed.data);
|
||||
lgClipboard_release();
|
||||
return;
|
||||
}
|
||||
|
||||
if (itemCount)
|
||||
{
|
||||
memcpy(x11cb.fileImport.data + x11cb.fileImport.size,
|
||||
data, (size_t)itemCount);
|
||||
x11cb.fileImport.size += (size_t)itemCount;
|
||||
}
|
||||
if (data)
|
||||
{
|
||||
XFree(data);
|
||||
data = NULL;
|
||||
}
|
||||
|
||||
if (after)
|
||||
{
|
||||
const unsigned long units = itemCount / 4U +
|
||||
(itemCount % 4U != 0);
|
||||
if (units > LONG_MAX ||
|
||||
x11cb.fileImport.propertyOffset > LONG_MAX - (long)units)
|
||||
{
|
||||
const struct X11ClipboardFileImport failed = takeFileImportNL();
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
free(failed.data);
|
||||
lgClipboard_release();
|
||||
return;
|
||||
}
|
||||
x11cb.fileImport.propertyOffset += (long)units;
|
||||
goto readProperty;
|
||||
}
|
||||
|
||||
x11cb.fileImport.propertyOffset = 0;
|
||||
if (itemCount)
|
||||
{
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
return;
|
||||
}
|
||||
|
||||
const struct X11ClipboardFileImport import = takeFileImportNL();
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
finishFileImport(import, import.data, import.size);
|
||||
}
|
||||
|
||||
enum X11ClipboardReadOperation
|
||||
{
|
||||
X11_CLIPBOARD_READ_BEGIN,
|
||||
@@ -781,6 +1286,10 @@ static void x11CBXFixesSelectionNotify(const XFixesSelectionNotifyEvent e)
|
||||
const LG_ClipboardRequest oldRequest = x11cb.read.window ?
|
||||
cancelReadNL(true) : LG_CLIPBOARD_REQUEST_INVALID;
|
||||
clearTargetsNL();
|
||||
clearFileImportNL();
|
||||
x11cb.haveRequest = false;
|
||||
const uint64_t presentation = x11cb.filePresentation;
|
||||
x11cb.filePresentation = 0;
|
||||
|
||||
if (e.owner == 0)
|
||||
{
|
||||
@@ -790,6 +1299,9 @@ static void x11CBXFixesSelectionNotify(const XFixesSelectionNotifyEvent e)
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
if (oldRequest != LG_CLIPBOARD_REQUEST_INVALID)
|
||||
lgClipboard_abort(oldRequest);
|
||||
if (presentation)
|
||||
lgClipboardFiles_remotePresentationRelease(presentation);
|
||||
lgClipboardFiles_clearLocal();
|
||||
lgClipboard_release();
|
||||
return;
|
||||
}
|
||||
@@ -806,6 +1318,9 @@ static void x11CBXFixesSelectionNotify(const XFixesSelectionNotifyEvent e)
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
if (oldRequest != LG_CLIPBOARD_REQUEST_INVALID)
|
||||
lgClipboard_abort(oldRequest);
|
||||
if (presentation)
|
||||
lgClipboardFiles_remotePresentationRelease(presentation);
|
||||
lgClipboardFiles_clearLocal();
|
||||
lgClipboard_release();
|
||||
return;
|
||||
}
|
||||
@@ -823,6 +1338,9 @@ static void x11CBXFixesSelectionNotify(const XFixesSelectionNotifyEvent e)
|
||||
|
||||
if (oldRequest != LG_CLIPBOARD_REQUEST_INVALID)
|
||||
lgClipboard_abort(oldRequest);
|
||||
if (presentation)
|
||||
lgClipboardFiles_remotePresentationRelease(presentation);
|
||||
lgClipboardFiles_clearLocal();
|
||||
}
|
||||
|
||||
static void x11CBSelectionNotify(const XSelectionEvent e)
|
||||
@@ -833,6 +1351,9 @@ static void x11CBSelectionNotify(const XSelectionEvent e)
|
||||
unsigned long after;
|
||||
unsigned char * data = NULL;
|
||||
|
||||
if (x11CBFileImportSelectionNotify(e))
|
||||
return;
|
||||
|
||||
LG_LOCK(x11cb.lock);
|
||||
const bool targetReply = x11cb.targetsWindow &&
|
||||
e.requestor == x11cb.targetsWindow && e.target == x11atoms.TARGETS;
|
||||
@@ -881,6 +1402,29 @@ static void x11CBSelectionNotify(const XSelectionEvent e)
|
||||
|
||||
// see if we support any of the targets listed
|
||||
const Atom * targets = (const Atom *)data;
|
||||
Atom fileTarget = None;
|
||||
for (unsigned long i = 0; i < itemCount; ++i)
|
||||
if (targets[i] == x11cb.aFileGnome)
|
||||
{
|
||||
fileTarget = x11cb.aFileGnome;
|
||||
break;
|
||||
}
|
||||
if (fileTarget == None)
|
||||
for (unsigned long i = 0; i < itemCount; ++i)
|
||||
if (targets[i] == x11cb.aTypes[LG_CLIPBOARD_DATA_FILES])
|
||||
{
|
||||
fileTarget = x11cb.aTypes[LG_CLIPBOARD_DATA_FILES];
|
||||
break;
|
||||
}
|
||||
|
||||
if (fileTarget != None)
|
||||
{
|
||||
lgClipboardFiles_clearLocal();
|
||||
if (!startFileImport(fileTarget))
|
||||
lgClipboard_release();
|
||||
goto out;
|
||||
}
|
||||
|
||||
for(int n = 0; n < LG_CLIPBOARD_DATA_NONE; ++n)
|
||||
for(unsigned long i = 0; i < itemCount; ++i)
|
||||
if (x11cb.aTypes[n] == targets[i])
|
||||
@@ -1010,24 +1554,35 @@ out:
|
||||
|
||||
void x11CBNotice(LG_ClipboardData type)
|
||||
{
|
||||
const uint64_t nextPresentation = type == LG_CLIPBOARD_DATA_FILES ?
|
||||
lgClipboardFiles_remotePresentationAcquire() : 0;
|
||||
LG_LOCK(x11cb.lock);
|
||||
const LG_ClipboardRequest oldRequest = x11cb.read.window ?
|
||||
cancelReadNL(true) : LG_CLIPBOARD_REQUEST_INVALID;
|
||||
clearTargetsNL();
|
||||
x11cb.aCurSelection = BadValue;
|
||||
x11cb.haveRequest = true;
|
||||
x11cb.type = type;
|
||||
XSetSelectionOwner(x11.display, x11atoms.CLIPBOARD, x11.window, CurrentTime);
|
||||
clearFileImportNL();
|
||||
const uint64_t oldPresentation = x11cb.filePresentation;
|
||||
x11cb.filePresentation = nextPresentation;
|
||||
x11cb.aCurSelection = BadValue;
|
||||
x11cb.haveRequest = type != LG_CLIPBOARD_DATA_FILES || nextPresentation;
|
||||
x11cb.type = type;
|
||||
XSetSelectionOwner(x11.display, x11atoms.CLIPBOARD,
|
||||
x11cb.haveRequest ? x11.window : None, CurrentTime);
|
||||
XFlush(x11.display);
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
if (oldRequest != LG_CLIPBOARD_REQUEST_INVALID)
|
||||
lgClipboard_abort(oldRequest);
|
||||
if (oldPresentation)
|
||||
lgClipboardFiles_remotePresentationRelease(oldPresentation);
|
||||
lgClipboardFiles_clearLocal();
|
||||
}
|
||||
|
||||
void x11CBRelease(void)
|
||||
{
|
||||
LG_LOCK(x11cb.lock);
|
||||
x11cb.haveRequest = false;
|
||||
const uint64_t presentation = x11cb.filePresentation;
|
||||
x11cb.filePresentation = 0;
|
||||
XGrabServer(x11.display);
|
||||
if (XGetSelectionOwner(x11.display, x11atoms.CLIPBOARD) == x11.window)
|
||||
XSetSelectionOwner(
|
||||
@@ -1035,6 +1590,26 @@ void x11CBRelease(void)
|
||||
XUngrabServer(x11.display);
|
||||
XFlush(x11.display);
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
if (presentation)
|
||||
lgClipboardFiles_remotePresentationRelease(presentation);
|
||||
}
|
||||
|
||||
void x11CBFree(void)
|
||||
{
|
||||
x11CBRelease();
|
||||
|
||||
LG_LOCK(x11cb.lock);
|
||||
const LG_ClipboardRequest request = x11cb.read.window ?
|
||||
cancelReadNL(true) : LG_CLIPBOARD_REQUEST_INVALID;
|
||||
clearTargetsNL();
|
||||
clearFileImportNL();
|
||||
struct X11ClipboardWrite * writes = takeFileWritesNL();
|
||||
LG_UNLOCK(x11cb.lock);
|
||||
|
||||
if (request != LG_CLIPBOARD_REQUEST_INVALID)
|
||||
lgClipboard_abort(request);
|
||||
cancelFileWrites(writes, true);
|
||||
lgClipboardFiles_clearLocal();
|
||||
}
|
||||
|
||||
void x11CBRequest(LG_ClipboardRequest request, LG_ClipboardData type)
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
bool x11CBEventThread(const XEvent * xe);
|
||||
|
||||
bool x11CBInit(void);
|
||||
void x11CBFree(void);
|
||||
void x11CBNotice(LG_ClipboardData type);
|
||||
void x11CBRelease(void);
|
||||
void x11CBRequest(LG_ClipboardRequest request, LG_ClipboardData type);
|
||||
|
||||
@@ -980,6 +980,7 @@ static void x11Shutdown(void)
|
||||
static void x11Free(void)
|
||||
{
|
||||
x11Shutdown();
|
||||
x11CBFree();
|
||||
|
||||
if (x11.jitRender)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user