mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 07:01:30 +00:00
[client] clipboard: add seamless file backends
This commit is contained in:
@@ -27,6 +27,7 @@ include(UninstallTarget)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(FONTCONFIG REQUIRED IMPORTED_TARGET fontconfig)
|
||||
pkg_check_modules(FUSE3 REQUIRED IMPORTED_TARGET fuse3>=3.10)
|
||||
|
||||
option(ENABLE_OPENGL "Enable the OpenGL renderer" ON)
|
||||
add_feature_info(ENABLE_OPENGL ENABLE_OPENGL "Legacy OpenGL renderer.")
|
||||
@@ -177,6 +178,7 @@ set(SOURCES
|
||||
src/font.c
|
||||
src/util.c
|
||||
src/clipboard.c
|
||||
src/clipboard_files.c
|
||||
src/kb.c
|
||||
src/kb_hid.c
|
||||
src/gl_dynprocs.c
|
||||
@@ -252,6 +254,7 @@ add_custom_command(TARGET looking-glass-client POST_BUILD
|
||||
target_link_libraries(looking-glass-client
|
||||
${EXE_FLAGS}
|
||||
PkgConfig::FONTCONFIG
|
||||
PkgConfig::FUSE3
|
||||
lg_resources
|
||||
lg_common
|
||||
displayservers
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -32,6 +32,7 @@ typedef enum LG_ClipboardData
|
||||
LG_CLIPBOARD_DATA_BMP,
|
||||
LG_CLIPBOARD_DATA_TIFF,
|
||||
LG_CLIPBOARD_DATA_JPEG,
|
||||
LG_CLIPBOARD_DATA_FILES,
|
||||
|
||||
LG_CLIPBOARD_DATA_NONE
|
||||
}
|
||||
@@ -65,6 +66,42 @@ typedef enum LG_ClipboardCancelReason
|
||||
}
|
||||
LG_ClipboardCancelReason;
|
||||
|
||||
typedef enum LG_ClipboardFileOperation
|
||||
{
|
||||
LG_CLIPBOARD_FILE_LIST = 1,
|
||||
LG_CLIPBOARD_FILE_READ = 2,
|
||||
}
|
||||
LG_ClipboardFileOperation;
|
||||
|
||||
typedef enum LG_ClipboardFileError
|
||||
{
|
||||
LG_CLIPBOARD_FILE_ERROR_NONE = 0,
|
||||
LG_CLIPBOARD_FILE_ERROR_NOT_FOUND,
|
||||
LG_CLIPBOARD_FILE_ERROR_ACCESS,
|
||||
LG_CLIPBOARD_FILE_ERROR_NOT_DIRECTORY,
|
||||
LG_CLIPBOARD_FILE_ERROR_IS_DIRECTORY,
|
||||
LG_CLIPBOARD_FILE_ERROR_IO,
|
||||
LG_CLIPBOARD_FILE_ERROR_INVALID,
|
||||
LG_CLIPBOARD_FILE_ERROR_NO_MEMORY,
|
||||
LG_CLIPBOARD_FILE_ERROR_NO_SPACE,
|
||||
LG_CLIPBOARD_FILE_ERROR_DISCONNECTED,
|
||||
LG_CLIPBOARD_FILE_ERROR_CANCELLED,
|
||||
LG_CLIPBOARD_FILE_ERROR_NOT_SUPPORTED,
|
||||
LG_CLIPBOARD_FILE_ERROR_STALE,
|
||||
}
|
||||
LG_ClipboardFileError;
|
||||
|
||||
typedef struct LG_ClipboardFileRequest
|
||||
{
|
||||
uint64_t dataset;
|
||||
uint64_t request;
|
||||
uint64_t node;
|
||||
uint64_t offset;
|
||||
uint32_t length;
|
||||
LG_ClipboardFileOperation operation;
|
||||
}
|
||||
LG_ClipboardFileRequest;
|
||||
|
||||
/* A consumer of a provider-to-client stream. Callbacks are serialized and
|
||||
* buffers are borrowed only for the duration of chunk(). A callback which
|
||||
* returns BLOCKED consumes nothing; the consumer must subsequently call
|
||||
@@ -116,6 +153,28 @@ typedef struct LG_ClipboardEventOps
|
||||
void (*release)(void * opaque);
|
||||
bool (*request)(void * opaque, LG_ClipboardRequest request,
|
||||
LG_ClipboardData type);
|
||||
|
||||
/* File datasets use independent, multiplexed request streams. Dataset and
|
||||
* node identifiers are opaque outside the publisher. */
|
||||
void (*fileOffer)(void * opaque, uint64_t dataset);
|
||||
void (*fileAcquire)(void * opaque, uint64_t dataset,
|
||||
uint64_t acquisition);
|
||||
void (*fileAcquired)(void * opaque, uint64_t dataset,
|
||||
uint64_t acquisition, LG_ClipboardFileError error);
|
||||
void (*fileRelease)(void * opaque, uint64_t dataset,
|
||||
uint64_t acquisition);
|
||||
void (*fileRequest)(void * opaque,
|
||||
const LG_ClipboardFileRequest * request);
|
||||
LG_ClipboardResult (*fileDataBegin)(void * opaque,
|
||||
const LG_ClipboardFileRequest * request, uint64_t sizeHint);
|
||||
LG_ClipboardResult (*fileDataChunk)(void * opaque,
|
||||
const LG_ClipboardFileRequest * request, uint64_t responseOffset,
|
||||
const void * data, size_t size);
|
||||
LG_ClipboardResult (*fileDataEnd)(void * opaque,
|
||||
const LG_ClipboardFileRequest * request, uint64_t finalSize);
|
||||
void (*fileDataReady)(void * opaque, uint64_t request);
|
||||
void (*fileCancel)(void * opaque, uint64_t dataset,
|
||||
uint64_t request, LG_ClipboardFileError reason);
|
||||
}
|
||||
LG_ClipboardEventOps;
|
||||
|
||||
@@ -144,6 +203,9 @@ typedef struct LG_ClipboardOps
|
||||
/* All outbound arrays and data are borrowed only until the call returns. */
|
||||
bool (*notifyTypes)(void * opaque, const LG_ClipboardData types[],
|
||||
size_t count);
|
||||
/* Publishes an immutable local file dataset. The nonzero dataset ID is
|
||||
* process-unique and is used as the wire clipboard generation. */
|
||||
bool (*offerFiles)(void * opaque, uint64_t dataset);
|
||||
/* Data is a complete response to a remote request. NONE with no payload
|
||||
* reports that the request could not be completed. This operation is the
|
||||
* legacy whole-buffer alternative to the stream operation group below. */
|
||||
@@ -174,6 +236,24 @@ typedef struct LG_ClipboardOps
|
||||
* or release first. */
|
||||
bool (*request)(void * opaque, LG_ClipboardRequest request,
|
||||
LG_ClipboardData type);
|
||||
|
||||
bool (*fileAcquire)(void * opaque, uint64_t dataset,
|
||||
uint64_t acquisition);
|
||||
bool (*fileAcquired)(void * opaque, uint64_t dataset,
|
||||
uint64_t acquisition, LG_ClipboardFileError error);
|
||||
bool (*fileRelease)(void * opaque, uint64_t dataset,
|
||||
uint64_t acquisition);
|
||||
bool (*fileRequest)(void * opaque,
|
||||
const LG_ClipboardFileRequest * request);
|
||||
LG_ClipboardResult (*fileDataBegin)(void * opaque,
|
||||
const LG_ClipboardFileRequest * request, uint64_t sizeHint);
|
||||
LG_ClipboardResult (*fileDataChunk)(void * opaque,
|
||||
const LG_ClipboardFileRequest * request, uint64_t responseOffset,
|
||||
const void * data, size_t size);
|
||||
LG_ClipboardResult (*fileDataEnd)(void * opaque,
|
||||
const LG_ClipboardFileRequest * request, uint64_t finalSize);
|
||||
bool (*fileCancel)(void * opaque, uint64_t dataset,
|
||||
uint64_t request, LG_ClipboardFileError reason);
|
||||
}
|
||||
LG_ClipboardOps;
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
*/
|
||||
|
||||
#include "clipboard.h"
|
||||
#include "clipboard_files.h"
|
||||
#include "main.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
@@ -83,6 +84,7 @@ static struct
|
||||
LG_Lock requestLock;
|
||||
LG_Lock writeLock;
|
||||
LG_Lock callbackLock;
|
||||
LG_Lock fileLock;
|
||||
|
||||
ClipboardBinding fallback;
|
||||
ClipboardBinding transport;
|
||||
@@ -91,10 +93,12 @@ static struct
|
||||
bool localAvailable;
|
||||
LG_ClipboardData localTypes[LG_CLIPBOARD_DATA_NONE];
|
||||
size_t localTypeCount;
|
||||
uint64_t localFileDataset;
|
||||
|
||||
bool remoteNotice;
|
||||
LG_ClipboardData remoteType;
|
||||
uint32_t remoteGeneration;
|
||||
uint64_t remoteFileDataset;
|
||||
|
||||
bool remoteRequest;
|
||||
ClipboardBinding remoteRequestBinding;
|
||||
@@ -371,18 +375,22 @@ static void resetRemote(void)
|
||||
bool release;
|
||||
LG_ClipboardRequest transfer;
|
||||
|
||||
LG_LOCK(clipboard.fileLock);
|
||||
LG_LOCK(clipboard.requestLock);
|
||||
LG_LOCK(clipboard.stateLock);
|
||||
release = clipboard.remoteNotice && clipboard.localAvailable &&
|
||||
g_params.clipboardToLocal;
|
||||
clipboard.remoteNotice = false;
|
||||
clipboard.remoteType = LG_CLIPBOARD_DATA_NONE;
|
||||
clipboard.remoteGeneration = nextGeneration(
|
||||
clipboard.remoteNotice = false;
|
||||
clipboard.remoteType = LG_CLIPBOARD_DATA_NONE;
|
||||
clipboard.remoteFileDataset = 0;
|
||||
clipboard.remoteGeneration = nextGeneration(
|
||||
clipboard.remoteGeneration);
|
||||
transfer = clearRemoteRequestNL();
|
||||
LG_UNLOCK(clipboard.stateLock);
|
||||
LG_UNLOCK(clipboard.requestLock);
|
||||
|
||||
lgClipboardFiles_providerUnavailable();
|
||||
|
||||
LG_LOCK(clipboard.callbackLock);
|
||||
cancelRequestsNL(NULL, 0, true, LG_CLIPBOARD_CANCEL_UNAVAILABLE);
|
||||
if (transfer != LG_CLIPBOARD_REQUEST_INVALID &&
|
||||
@@ -396,6 +404,7 @@ static void resetRemote(void)
|
||||
if (release)
|
||||
g_state.ds->cbRelease();
|
||||
LG_UNLOCK(clipboard.callbackLock);
|
||||
LG_UNLOCK(clipboard.fileLock);
|
||||
}
|
||||
|
||||
static bool validOps(const LG_ClipboardOps * ops)
|
||||
@@ -435,11 +444,18 @@ static void publishLocal(const ClipboardBinding * binding)
|
||||
LG_LOCK(clipboard.stateLock);
|
||||
count = g_params.clipboardToVM ? clipboard.localTypeCount : 0;
|
||||
memcpy(types, clipboard.localTypes, count * sizeof(*types));
|
||||
const uint64_t fileDataset = clipboard.localFileDataset;
|
||||
LG_UNLOCK(clipboard.stateLock);
|
||||
|
||||
const bool result = count ?
|
||||
binding->ops->notifyTypes(binding->opaque, types, count) :
|
||||
binding->ops->release(binding->opaque);
|
||||
bool result;
|
||||
if (count == 1 && types[0] == LG_CLIPBOARD_DATA_FILES)
|
||||
result = fileDataset && binding->ops->offerFiles ?
|
||||
binding->ops->offerFiles(binding->opaque, fileDataset) :
|
||||
binding->ops->release(binding->opaque);
|
||||
else
|
||||
result = count ? binding->ops->notifyTypes(
|
||||
binding->opaque, types, count) :
|
||||
binding->ops->release(binding->opaque);
|
||||
if (!result)
|
||||
DEBUG_WARN("Failed to publish the local clipboard to %s",
|
||||
binding->ops->name);
|
||||
@@ -454,39 +470,60 @@ static void eventNotice(void * opaque,
|
||||
return;
|
||||
|
||||
LG_ClipboardData type = LG_CLIPBOARD_DATA_NONE;
|
||||
bool hasFiles = false;
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
if (validType(types[i]))
|
||||
{
|
||||
type = types[i];
|
||||
break;
|
||||
if (types[i] == LG_CLIPBOARD_DATA_FILES)
|
||||
{
|
||||
type = LG_CLIPBOARD_DATA_FILES;
|
||||
hasFiles = true;
|
||||
}
|
||||
else if (type == LG_CLIPBOARD_DATA_NONE)
|
||||
type = types[i];
|
||||
}
|
||||
if (type == LG_CLIPBOARD_DATA_NONE)
|
||||
return;
|
||||
|
||||
LG_LOCK(clipboard.fileLock);
|
||||
LG_LOCK_SHARED(clipboard.activeLock);
|
||||
if (!bindingActiveNL(binding))
|
||||
{
|
||||
LG_UNLOCK_SHARED(clipboard.activeLock);
|
||||
LG_UNLOCK(clipboard.fileLock);
|
||||
return;
|
||||
}
|
||||
|
||||
const ClipboardBinding current = *binding;
|
||||
LG_UNLOCK_SHARED(clipboard.activeLock);
|
||||
bool notice;
|
||||
bool release;
|
||||
uint32_t generation;
|
||||
uint64_t fileDataset;
|
||||
LG_ClipboardRequest transfer;
|
||||
|
||||
LG_LOCK(clipboard.requestLock);
|
||||
LG_LOCK(clipboard.stateLock);
|
||||
clipboard.remoteNotice = true;
|
||||
clipboard.remoteType = type;
|
||||
const bool previousNotice = clipboard.remoteNotice;
|
||||
const bool usable = !hasFiles || clipboard.remoteFileDataset;
|
||||
clipboard.remoteNotice = usable;
|
||||
clipboard.remoteType = usable ? type : LG_CLIPBOARD_DATA_NONE;
|
||||
clipboard.remoteGeneration = nextGeneration(
|
||||
clipboard.remoteGeneration);
|
||||
generation = clipboard.remoteGeneration;
|
||||
if (!hasFiles || !usable)
|
||||
clipboard.remoteFileDataset = 0;
|
||||
fileDataset = clipboard.remoteFileDataset;
|
||||
transfer = clearRemoteRequestNL();
|
||||
notice = clipboard.localAvailable && g_params.clipboardToLocal;
|
||||
notice = usable && clipboard.localAvailable &&
|
||||
g_params.clipboardToLocal;
|
||||
release = !usable && previousNotice && clipboard.localAvailable &&
|
||||
g_params.clipboardToLocal;
|
||||
LG_UNLOCK(clipboard.stateLock);
|
||||
LG_UNLOCK(clipboard.requestLock);
|
||||
if (!hasFiles)
|
||||
lgClipboardFiles_remoteClear();
|
||||
LG_UNLOCK(clipboard.fileLock);
|
||||
|
||||
LG_LOCK(clipboard.callbackLock);
|
||||
cancelRequestsNL(¤t, generation, false,
|
||||
@@ -498,12 +535,21 @@ static void eventNotice(void * opaque,
|
||||
notice = notice && clipboard.localAvailable &&
|
||||
clipboard.remoteNotice &&
|
||||
clipboard.remoteGeneration == generation;
|
||||
release = release && clipboard.localAvailable &&
|
||||
!clipboard.remoteNotice &&
|
||||
clipboard.remoteGeneration == generation;
|
||||
LG_UNLOCK(clipboard.stateLock);
|
||||
LG_LOCK_SHARED(clipboard.activeLock);
|
||||
const bool active = bindingActiveNL(¤t);
|
||||
LG_UNLOCK_SHARED(clipboard.activeLock);
|
||||
if (notice && active)
|
||||
g_state.ds->cbNotice(type);
|
||||
{
|
||||
if (type != LG_CLIPBOARD_DATA_FILES ||
|
||||
lgClipboardFiles_remoteReady(fileDataset))
|
||||
g_state.ds->cbNotice(type);
|
||||
}
|
||||
else if (release && active && g_state.ds->cbRelease)
|
||||
g_state.ds->cbRelease();
|
||||
LG_UNLOCK(clipboard.callbackLock);
|
||||
}
|
||||
|
||||
@@ -892,14 +938,19 @@ static void eventRelease(void * opaque)
|
||||
LG_LOCK(clipboard.stateLock);
|
||||
release = clipboard.remoteNotice && clipboard.localAvailable &&
|
||||
g_params.clipboardToLocal;
|
||||
clipboard.remoteNotice = false;
|
||||
clipboard.remoteType = LG_CLIPBOARD_DATA_NONE;
|
||||
clipboard.remoteGeneration = nextGeneration(
|
||||
clipboard.remoteNotice = false;
|
||||
clipboard.remoteType = LG_CLIPBOARD_DATA_NONE;
|
||||
clipboard.remoteFileDataset = 0;
|
||||
clipboard.remoteGeneration = nextGeneration(
|
||||
clipboard.remoteGeneration);
|
||||
transfer = clearRemoteRequestNL();
|
||||
LG_UNLOCK(clipboard.stateLock);
|
||||
LG_UNLOCK(clipboard.requestLock);
|
||||
|
||||
LG_LOCK(clipboard.fileLock);
|
||||
lgClipboardFiles_remoteClear();
|
||||
LG_UNLOCK(clipboard.fileLock);
|
||||
|
||||
LG_LOCK(clipboard.callbackLock);
|
||||
cancelRequestsNL(NULL, 0, true, LG_CLIPBOARD_CANCEL_REPLACED);
|
||||
if (transfer != LG_CLIPBOARD_REQUEST_INVALID &&
|
||||
@@ -1024,6 +1075,163 @@ static void eventRequestCancel(void * opaque, LG_ClipboardRequest id,
|
||||
LG_UNLOCK(clipboard.callbackLock);
|
||||
}
|
||||
|
||||
static bool fileEventBindingActive(void * opaque)
|
||||
{
|
||||
ClipboardBinding * binding = opaque;
|
||||
LG_LOCK_SHARED(clipboard.activeLock);
|
||||
const bool active = bindingActiveNL(binding);
|
||||
LG_UNLOCK_SHARED(clipboard.activeLock);
|
||||
return active;
|
||||
}
|
||||
|
||||
static void eventFileOffer(void * opaque, uint64_t dataset)
|
||||
{
|
||||
LG_LOCK(clipboard.fileLock);
|
||||
const bool active = fileEventBindingActive(opaque);
|
||||
if (!active || !dataset)
|
||||
{
|
||||
LG_UNLOCK(clipboard.fileLock);
|
||||
return;
|
||||
}
|
||||
|
||||
LG_LOCK(clipboard.stateLock);
|
||||
clipboard.remoteFileDataset = dataset;
|
||||
LG_UNLOCK(clipboard.stateLock);
|
||||
if (!lgClipboardFiles_remoteOffer(dataset))
|
||||
{
|
||||
LG_LOCK(clipboard.stateLock);
|
||||
if (clipboard.remoteFileDataset == dataset)
|
||||
clipboard.remoteFileDataset = 0;
|
||||
LG_UNLOCK(clipboard.stateLock);
|
||||
lgClipboardFiles_remoteClear();
|
||||
}
|
||||
LG_UNLOCK(clipboard.fileLock);
|
||||
}
|
||||
|
||||
static void eventFileAcquire(void * opaque, uint64_t dataset,
|
||||
uint64_t acquisition)
|
||||
{
|
||||
LG_LOCK(clipboard.fileLock);
|
||||
if (!fileEventBindingActive(opaque))
|
||||
{
|
||||
LG_UNLOCK(clipboard.fileLock);
|
||||
return;
|
||||
}
|
||||
lgClipboardFiles_localAcquire(dataset, acquisition);
|
||||
LG_UNLOCK(clipboard.fileLock);
|
||||
}
|
||||
|
||||
static void eventFileAcquired(void * opaque, uint64_t dataset,
|
||||
uint64_t acquisition, LG_ClipboardFileError error)
|
||||
{
|
||||
LG_LOCK(clipboard.fileLock);
|
||||
if (!fileEventBindingActive(opaque))
|
||||
{
|
||||
LG_UNLOCK(clipboard.fileLock);
|
||||
return;
|
||||
}
|
||||
lgClipboardFiles_remoteAcquired(dataset, acquisition, error);
|
||||
LG_UNLOCK(clipboard.fileLock);
|
||||
}
|
||||
|
||||
static void eventFileRelease(void * opaque, uint64_t dataset,
|
||||
uint64_t acquisition)
|
||||
{
|
||||
LG_LOCK(clipboard.fileLock);
|
||||
if (!fileEventBindingActive(opaque))
|
||||
{
|
||||
LG_UNLOCK(clipboard.fileLock);
|
||||
return;
|
||||
}
|
||||
lgClipboardFiles_localRelease(dataset, acquisition);
|
||||
LG_UNLOCK(clipboard.fileLock);
|
||||
}
|
||||
|
||||
static void eventFileRequest(void * opaque,
|
||||
const LG_ClipboardFileRequest * request)
|
||||
{
|
||||
LG_LOCK(clipboard.fileLock);
|
||||
if (!fileEventBindingActive(opaque))
|
||||
{
|
||||
LG_UNLOCK(clipboard.fileLock);
|
||||
return;
|
||||
}
|
||||
lgClipboardFiles_localRequest(request);
|
||||
LG_UNLOCK(clipboard.fileLock);
|
||||
}
|
||||
|
||||
static LG_ClipboardResult eventFileDataBegin(void * opaque,
|
||||
const LG_ClipboardFileRequest * request, uint64_t sizeHint)
|
||||
{
|
||||
LG_LOCK(clipboard.fileLock);
|
||||
if (!fileEventBindingActive(opaque))
|
||||
{
|
||||
LG_UNLOCK(clipboard.fileLock);
|
||||
return LG_CLIPBOARD_RESULT_FAILED;
|
||||
}
|
||||
const LG_ClipboardResult result =
|
||||
lgClipboardFiles_remoteDataBegin(request, sizeHint);
|
||||
LG_UNLOCK(clipboard.fileLock);
|
||||
return result;
|
||||
}
|
||||
|
||||
static LG_ClipboardResult eventFileDataChunk(void * opaque,
|
||||
const LG_ClipboardFileRequest * request, uint64_t responseOffset,
|
||||
const void * data, size_t size)
|
||||
{
|
||||
LG_LOCK(clipboard.fileLock);
|
||||
if (!fileEventBindingActive(opaque))
|
||||
{
|
||||
LG_UNLOCK(clipboard.fileLock);
|
||||
return LG_CLIPBOARD_RESULT_FAILED;
|
||||
}
|
||||
const LG_ClipboardResult result = lgClipboardFiles_remoteDataChunk(
|
||||
request, responseOffset, data, size);
|
||||
LG_UNLOCK(clipboard.fileLock);
|
||||
return result;
|
||||
}
|
||||
|
||||
static LG_ClipboardResult eventFileDataEnd(void * opaque,
|
||||
const LG_ClipboardFileRequest * request, uint64_t finalSize)
|
||||
{
|
||||
LG_LOCK(clipboard.fileLock);
|
||||
if (!fileEventBindingActive(opaque))
|
||||
{
|
||||
LG_UNLOCK(clipboard.fileLock);
|
||||
return LG_CLIPBOARD_RESULT_FAILED;
|
||||
}
|
||||
const LG_ClipboardResult result =
|
||||
lgClipboardFiles_remoteDataEnd(request, finalSize);
|
||||
LG_UNLOCK(clipboard.fileLock);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void eventFileDataReady(void * opaque, uint64_t request)
|
||||
{
|
||||
LG_LOCK(clipboard.fileLock);
|
||||
if (!fileEventBindingActive(opaque))
|
||||
{
|
||||
LG_UNLOCK(clipboard.fileLock);
|
||||
return;
|
||||
}
|
||||
lgClipboardFiles_localReady(request);
|
||||
LG_UNLOCK(clipboard.fileLock);
|
||||
}
|
||||
|
||||
static void eventFileCancel(void * opaque, uint64_t dataset,
|
||||
uint64_t request, LG_ClipboardFileError reason)
|
||||
{
|
||||
LG_LOCK(clipboard.fileLock);
|
||||
if (!fileEventBindingActive(opaque))
|
||||
{
|
||||
LG_UNLOCK(clipboard.fileLock);
|
||||
return;
|
||||
}
|
||||
lgClipboardFiles_remoteCancel(dataset, request, reason);
|
||||
lgClipboardFiles_localCancel(dataset, request, reason);
|
||||
LG_UNLOCK(clipboard.fileLock);
|
||||
}
|
||||
|
||||
static const LG_ClipboardEventOps eventOps =
|
||||
{
|
||||
.notice = eventNotice,
|
||||
@@ -1036,6 +1244,16 @@ static const LG_ClipboardEventOps eventOps =
|
||||
.requestCancel = eventRequestCancel,
|
||||
.release = eventRelease,
|
||||
.request = eventRequest,
|
||||
.fileOffer = eventFileOffer,
|
||||
.fileAcquire = eventFileAcquire,
|
||||
.fileAcquired = eventFileAcquired,
|
||||
.fileRelease = eventFileRelease,
|
||||
.fileRequest = eventFileRequest,
|
||||
.fileDataBegin = eventFileDataBegin,
|
||||
.fileDataChunk = eventFileDataChunk,
|
||||
.fileDataEnd = eventFileDataEnd,
|
||||
.fileDataReady = eventFileDataReady,
|
||||
.fileCancel = eventFileCancel,
|
||||
};
|
||||
|
||||
/* providerLock must be held. dropActive suppresses all calls into an endpoint
|
||||
@@ -1168,7 +1386,7 @@ static void setBinding(ClipboardBinding * target,
|
||||
LG_UNLOCK(clipboard.registrationLock);
|
||||
}
|
||||
|
||||
void lgClipboard_init(void)
|
||||
bool lgClipboard_init(void)
|
||||
{
|
||||
memset(&clipboard, 0, sizeof(clipboard));
|
||||
LG_LOCK_INIT(clipboard.registrationLock);
|
||||
@@ -1178,9 +1396,16 @@ void lgClipboard_init(void)
|
||||
LG_LOCK_INIT(clipboard.requestLock);
|
||||
LG_LOCK_INIT(clipboard.writeLock);
|
||||
LG_LOCK_INIT(clipboard.callbackLock);
|
||||
LG_LOCK_INIT(clipboard.fileLock);
|
||||
clipboard.remoteType = LG_CLIPBOARD_DATA_NONE;
|
||||
clipboard.remoteRequestType = LG_CLIPBOARD_DATA_NONE;
|
||||
clipboard.requests = ll_new();
|
||||
if (!lgClipboardFiles_init())
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize clipboard file transfer");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void lgClipboard_free(void)
|
||||
@@ -1188,6 +1413,7 @@ void lgClipboard_free(void)
|
||||
lgClipboard_setLocalAvailable(false);
|
||||
lgClipboard_setTransport(NULL, NULL);
|
||||
lgClipboard_setFallback(NULL, NULL);
|
||||
lgClipboardFiles_free();
|
||||
LG_LOCK(clipboard.callbackLock);
|
||||
cancelRequestsNL(NULL, 0, true, LG_CLIPBOARD_CANCEL_UNAVAILABLE);
|
||||
LG_UNLOCK(clipboard.callbackLock);
|
||||
@@ -1201,6 +1427,7 @@ void lgClipboard_free(void)
|
||||
LG_LOCK_FREE(clipboard.requestLock);
|
||||
LG_LOCK_FREE(clipboard.stateLock);
|
||||
LG_LOCK_FREE(clipboard.callbackLock);
|
||||
LG_LOCK_FREE(clipboard.fileLock);
|
||||
LG_RWLOCK_FREE(clipboard.activeLock);
|
||||
LG_LOCK_FREE(clipboard.providerLock);
|
||||
LG_LOCK_FREE(clipboard.registrationLock);
|
||||
@@ -1211,6 +1438,7 @@ void lgClipboard_setLocalAvailable(bool available)
|
||||
bool notice;
|
||||
bool release;
|
||||
LG_ClipboardData type;
|
||||
uint64_t fileDataset;
|
||||
LG_ClipboardRequest transfer = LG_CLIPBOARD_REQUEST_INVALID;
|
||||
|
||||
LG_LOCK(clipboard.callbackLock);
|
||||
@@ -1222,13 +1450,18 @@ void lgClipboard_setLocalAvailable(bool available)
|
||||
notice = available && clipboard.remoteNotice &&
|
||||
g_params.clipboardToLocal;
|
||||
type = clipboard.remoteType;
|
||||
fileDataset = clipboard.remoteFileDataset;
|
||||
if (!available)
|
||||
transfer = clearRemoteRequestNL();
|
||||
LG_UNLOCK(clipboard.stateLock);
|
||||
LG_UNLOCK(clipboard.requestLock);
|
||||
|
||||
if (notice)
|
||||
g_state.ds->cbNotice(type);
|
||||
{
|
||||
if (type != LG_CLIPBOARD_DATA_FILES ||
|
||||
lgClipboardFiles_remoteReady(fileDataset))
|
||||
g_state.ds->cbNotice(type);
|
||||
}
|
||||
if (!available)
|
||||
{
|
||||
cancelRequestsNL(NULL, 0, true, LG_CLIPBOARD_CANCEL_UNAVAILABLE);
|
||||
@@ -1285,6 +1518,7 @@ void lgClipboard_release(void)
|
||||
LG_LOCK(clipboard.writeLock);
|
||||
LG_LOCK(clipboard.stateLock);
|
||||
clipboard.localTypeCount = 0;
|
||||
clipboard.localFileDataset = 0;
|
||||
clearRemoteRequestNL();
|
||||
LG_UNLOCK(clipboard.stateLock);
|
||||
|
||||
@@ -1310,13 +1544,14 @@ void lgClipboard_notifyTypes(
|
||||
return;
|
||||
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
if (!validType(types[i]))
|
||||
if (!validType(types[i]) || types[i] == LG_CLIPBOARD_DATA_FILES)
|
||||
return;
|
||||
|
||||
LG_LOCK(clipboard.writeLock);
|
||||
LG_LOCK(clipboard.stateLock);
|
||||
memcpy(clipboard.localTypes, types, count * sizeof(*types));
|
||||
clipboard.localTypeCount = count;
|
||||
clipboard.localFileDataset = 0;
|
||||
clearRemoteRequestNL();
|
||||
LG_UNLOCK(clipboard.stateLock);
|
||||
|
||||
@@ -1329,6 +1564,30 @@ void lgClipboard_notifyTypes(
|
||||
LG_UNLOCK(clipboard.writeLock);
|
||||
}
|
||||
|
||||
void lgClipboard_notifyFiles(uint64_t dataset)
|
||||
{
|
||||
if (!g_params.clipboardToVM || !dataset)
|
||||
return;
|
||||
|
||||
LG_LOCK(clipboard.writeLock);
|
||||
LG_LOCK(clipboard.stateLock);
|
||||
clipboard.localTypes[0] = LG_CLIPBOARD_DATA_FILES;
|
||||
clipboard.localTypeCount = 1;
|
||||
clipboard.localFileDataset = dataset;
|
||||
clearRemoteRequestNL();
|
||||
LG_UNLOCK(clipboard.stateLock);
|
||||
|
||||
LG_LOCK_SHARED(clipboard.activeLock);
|
||||
const bool result = !clipboard.active.ops ||
|
||||
(clipboard.active.ops->offerFiles ?
|
||||
clipboard.active.ops->offerFiles(clipboard.active.opaque, dataset) :
|
||||
clipboard.active.ops->release(clipboard.active.opaque));
|
||||
if (!result)
|
||||
DEBUG_WARN("Failed to publish the local clipboard file dataset");
|
||||
LG_UNLOCK_SHARED(clipboard.activeLock);
|
||||
LG_UNLOCK(clipboard.writeLock);
|
||||
}
|
||||
|
||||
/* writeLock and activeLock must be held. */
|
||||
static bool cancelRemoteNL(LG_ClipboardCancelReason reason)
|
||||
{
|
||||
@@ -1864,3 +2123,139 @@ bool lgClipboard_request(LG_ClipboardData type,
|
||||
return false;
|
||||
return requestClipboard(type, replyFn, NULL, opaque, NULL);
|
||||
}
|
||||
|
||||
bool lgClipboard_fileAcquire(uint64_t dataset, uint64_t acquisition)
|
||||
{
|
||||
bool result = false;
|
||||
LG_LOCK_SHARED(clipboard.activeLock);
|
||||
if (clipboard.active.ops && clipboard.active.ops->fileAcquire)
|
||||
result = clipboard.active.ops->fileAcquire(
|
||||
clipboard.active.opaque, dataset, acquisition);
|
||||
LG_UNLOCK_SHARED(clipboard.activeLock);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool lgClipboard_fileAcquired(uint64_t dataset, uint64_t acquisition,
|
||||
LG_ClipboardFileError error)
|
||||
{
|
||||
bool result = false;
|
||||
LG_LOCK_SHARED(clipboard.activeLock);
|
||||
if (clipboard.active.ops && clipboard.active.ops->fileAcquired)
|
||||
result = clipboard.active.ops->fileAcquired(
|
||||
clipboard.active.opaque, dataset, acquisition, error);
|
||||
LG_UNLOCK_SHARED(clipboard.activeLock);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool lgClipboard_fileRelease(uint64_t dataset, uint64_t acquisition)
|
||||
{
|
||||
bool result = false;
|
||||
LG_LOCK_SHARED(clipboard.activeLock);
|
||||
if (clipboard.active.ops && clipboard.active.ops->fileRelease)
|
||||
result = clipboard.active.ops->fileRelease(
|
||||
clipboard.active.opaque, dataset, acquisition);
|
||||
LG_UNLOCK_SHARED(clipboard.activeLock);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool lgClipboard_fileRequest(const LG_ClipboardFileRequest * request)
|
||||
{
|
||||
bool result = false;
|
||||
LG_LOCK_SHARED(clipboard.activeLock);
|
||||
if (clipboard.active.ops && clipboard.active.ops->fileRequest)
|
||||
result = clipboard.active.ops->fileRequest(
|
||||
clipboard.active.opaque, request);
|
||||
LG_UNLOCK_SHARED(clipboard.activeLock);
|
||||
return result;
|
||||
}
|
||||
|
||||
LG_ClipboardResult lgClipboard_fileDataBegin(
|
||||
const LG_ClipboardFileRequest * request, uint64_t sizeHint)
|
||||
{
|
||||
LG_ClipboardResult result = LG_CLIPBOARD_RESULT_FAILED;
|
||||
LG_LOCK_SHARED(clipboard.activeLock);
|
||||
if (clipboard.active.ops && clipboard.active.ops->fileDataBegin)
|
||||
result = clipboard.active.ops->fileDataBegin(
|
||||
clipboard.active.opaque, request, sizeHint);
|
||||
LG_UNLOCK_SHARED(clipboard.activeLock);
|
||||
return result;
|
||||
}
|
||||
|
||||
LG_ClipboardResult lgClipboard_fileDataChunk(
|
||||
const LG_ClipboardFileRequest * request, uint64_t responseOffset,
|
||||
const void * data, size_t size)
|
||||
{
|
||||
LG_ClipboardResult result = LG_CLIPBOARD_RESULT_FAILED;
|
||||
LG_LOCK_SHARED(clipboard.activeLock);
|
||||
if (clipboard.active.ops && clipboard.active.ops->fileDataChunk)
|
||||
result = clipboard.active.ops->fileDataChunk(
|
||||
clipboard.active.opaque, request, responseOffset, data, size);
|
||||
LG_UNLOCK_SHARED(clipboard.activeLock);
|
||||
return result;
|
||||
}
|
||||
|
||||
LG_ClipboardResult lgClipboard_fileDataEnd(
|
||||
const LG_ClipboardFileRequest * request, uint64_t finalSize)
|
||||
{
|
||||
LG_ClipboardResult result = LG_CLIPBOARD_RESULT_FAILED;
|
||||
LG_LOCK_SHARED(clipboard.activeLock);
|
||||
if (clipboard.active.ops && clipboard.active.ops->fileDataEnd)
|
||||
result = clipboard.active.ops->fileDataEnd(
|
||||
clipboard.active.opaque, request, finalSize);
|
||||
LG_UNLOCK_SHARED(clipboard.activeLock);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool lgClipboard_fileCancel(uint64_t dataset, uint64_t request,
|
||||
LG_ClipboardFileError reason)
|
||||
{
|
||||
bool result = false;
|
||||
LG_LOCK_SHARED(clipboard.activeLock);
|
||||
if (clipboard.active.ops && clipboard.active.ops->fileCancel)
|
||||
result = clipboard.active.ops->fileCancel(
|
||||
clipboard.active.opaque, dataset, request, reason);
|
||||
LG_UNLOCK_SHARED(clipboard.activeLock);
|
||||
return result;
|
||||
}
|
||||
|
||||
void lgClipboard_fileRemoteReady(uint64_t dataset)
|
||||
{
|
||||
LG_LOCK(clipboard.callbackLock);
|
||||
LG_LOCK(clipboard.stateLock);
|
||||
const bool notice = clipboard.localAvailable &&
|
||||
g_params.clipboardToLocal && clipboard.remoteNotice &&
|
||||
clipboard.remoteType == LG_CLIPBOARD_DATA_FILES &&
|
||||
clipboard.remoteFileDataset == dataset;
|
||||
LG_UNLOCK(clipboard.stateLock);
|
||||
if (notice && g_state.ds->cbNotice)
|
||||
g_state.ds->cbNotice(LG_CLIPBOARD_DATA_FILES);
|
||||
LG_UNLOCK(clipboard.callbackLock);
|
||||
}
|
||||
|
||||
void lgClipboard_fileRemoteFailed(uint64_t dataset)
|
||||
{
|
||||
if (!dataset)
|
||||
return;
|
||||
|
||||
LG_LOCK(clipboard.callbackLock);
|
||||
LG_LOCK(clipboard.stateLock);
|
||||
const bool matchingDataset =
|
||||
clipboard.remoteFileDataset == dataset;
|
||||
const bool matchingNotice = matchingDataset && clipboard.remoteNotice &&
|
||||
clipboard.remoteType == LG_CLIPBOARD_DATA_FILES;
|
||||
const bool release = matchingNotice && clipboard.localAvailable &&
|
||||
g_params.clipboardToLocal;
|
||||
if (matchingDataset)
|
||||
clipboard.remoteFileDataset = 0;
|
||||
if (matchingNotice)
|
||||
{
|
||||
clipboard.remoteNotice = false;
|
||||
clipboard.remoteType = LG_CLIPBOARD_DATA_NONE;
|
||||
clipboard.remoteGeneration = nextGeneration(
|
||||
clipboard.remoteGeneration);
|
||||
}
|
||||
LG_UNLOCK(clipboard.stateLock);
|
||||
if (release && g_state.ds->cbRelease)
|
||||
g_state.ds->cbRelease();
|
||||
LG_UNLOCK(clipboard.callbackLock);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
#include "interface/clipboard.h"
|
||||
|
||||
void lgClipboard_init(void);
|
||||
bool lgClipboard_init(void);
|
||||
void lgClipboard_free(void);
|
||||
void lgClipboard_setLocalAvailable(bool available);
|
||||
|
||||
@@ -36,6 +36,7 @@ void lgClipboard_dropTransport(void);
|
||||
void lgClipboard_release(void);
|
||||
void lgClipboard_notifyTypes(
|
||||
const LG_ClipboardData types[], size_t count);
|
||||
void lgClipboard_notifyFiles(uint64_t dataset);
|
||||
LG_ClipboardResult lgClipboard_dataBegin(LG_ClipboardRequest request,
|
||||
LG_ClipboardData type, uint64_t sizeHint);
|
||||
LG_ClipboardResult lgClipboard_dataChunk(LG_ClipboardRequest request,
|
||||
@@ -55,4 +56,22 @@ bool lgClipboard_requestReady(LG_ClipboardRequest request);
|
||||
bool lgClipboard_request(LG_ClipboardData type,
|
||||
LG_ClipboardReplyFn replyFn, void * opaque);
|
||||
|
||||
/* File-dataset transport entry points used by clipboard_files.c. */
|
||||
bool lgClipboard_fileAcquire(uint64_t dataset, uint64_t acquisition);
|
||||
bool lgClipboard_fileAcquired(uint64_t dataset, uint64_t acquisition,
|
||||
LG_ClipboardFileError error);
|
||||
bool lgClipboard_fileRelease(uint64_t dataset, uint64_t acquisition);
|
||||
bool lgClipboard_fileRequest(const LG_ClipboardFileRequest * request);
|
||||
LG_ClipboardResult lgClipboard_fileDataBegin(
|
||||
const LG_ClipboardFileRequest * request, uint64_t sizeHint);
|
||||
LG_ClipboardResult lgClipboard_fileDataChunk(
|
||||
const LG_ClipboardFileRequest * request, uint64_t responseOffset,
|
||||
const void * data, size_t size);
|
||||
LG_ClipboardResult lgClipboard_fileDataEnd(
|
||||
const LG_ClipboardFileRequest * request, uint64_t finalSize);
|
||||
bool lgClipboard_fileCancel(uint64_t dataset, uint64_t request,
|
||||
LG_ClipboardFileError reason);
|
||||
void lgClipboard_fileRemoteReady(uint64_t dataset);
|
||||
void lgClipboard_fileRemoteFailed(uint64_t dataset);
|
||||
|
||||
#endif
|
||||
|
||||
3703
client/src/clipboard_files.c
Normal file
3703
client/src/clipboard_files.c
Normal file
File diff suppressed because it is too large
Load Diff
86
client/src/clipboard_files.h
Normal file
86
client/src/clipboard_files.h
Normal file
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2026 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef _H_LG_CLIENT_CLIPBOARD_FILES_
|
||||
#define _H_LG_CLIENT_CLIPBOARD_FILES_
|
||||
|
||||
#include "interface/clipboard.h"
|
||||
|
||||
bool lgClipboardFiles_init(void);
|
||||
void lgClipboardFiles_free(void);
|
||||
|
||||
/* Desktop backends install a local file selection after reading one of the
|
||||
* standard URI clipboard representations. The payload is copied. */
|
||||
bool lgClipboardFiles_setLocal(const char * mime,
|
||||
const void * data, size_t size);
|
||||
void lgClipboardFiles_clearLocal(void);
|
||||
|
||||
/* Returns an allocated desktop representation for the current guest offer. */
|
||||
bool lgClipboardFiles_getRemote(const char * mime,
|
||||
char ** data, size_t * size);
|
||||
uint64_t lgClipboardFiles_remotePresentationAcquire(void);
|
||||
bool lgClipboardFiles_getRemotePresentation(uint64_t presentation,
|
||||
const char * mime, char ** data, size_t * size);
|
||||
void lgClipboardFiles_remotePresentationDelivered(uint64_t presentation);
|
||||
void lgClipboardFiles_remotePresentationRelease(uint64_t presentation);
|
||||
bool lgClipboardFiles_remoteReady(uint64_t dataset);
|
||||
|
||||
/* Events delivered by the active clipboard transport. */
|
||||
bool lgClipboardFiles_remoteOffer(uint64_t dataset);
|
||||
void lgClipboardFiles_remoteClear(void);
|
||||
void lgClipboardFiles_providerUnavailable(void);
|
||||
void lgClipboardFiles_remoteAcquired(uint64_t dataset,
|
||||
uint64_t acquisition, LG_ClipboardFileError error);
|
||||
LG_ClipboardResult lgClipboardFiles_remoteDataBegin(
|
||||
const LG_ClipboardFileRequest * request, uint64_t sizeHint);
|
||||
LG_ClipboardResult lgClipboardFiles_remoteDataChunk(
|
||||
const LG_ClipboardFileRequest * request, uint64_t responseOffset,
|
||||
const void * data, size_t size);
|
||||
LG_ClipboardResult lgClipboardFiles_remoteDataEnd(
|
||||
const LG_ClipboardFileRequest * request, uint64_t finalSize);
|
||||
void lgClipboardFiles_remoteCancel(uint64_t dataset,
|
||||
uint64_t request, LG_ClipboardFileError reason);
|
||||
|
||||
void lgClipboardFiles_localAcquire(uint64_t dataset,
|
||||
uint64_t acquisition);
|
||||
void lgClipboardFiles_localRelease(uint64_t dataset,
|
||||
uint64_t acquisition);
|
||||
void lgClipboardFiles_localRequest(
|
||||
const LG_ClipboardFileRequest * request);
|
||||
void lgClipboardFiles_localCancel(uint64_t dataset,
|
||||
uint64_t request, LG_ClipboardFileError reason);
|
||||
void lgClipboardFiles_localReady(uint64_t request);
|
||||
|
||||
#ifdef ENABLE_TESTS
|
||||
bool lgClipboardFiles_testInit(uint64_t nonce);
|
||||
bool lgClipboardFiles_testFuseStopWake(void);
|
||||
bool lgClipboardFiles_testUnsentRemoteOwnership(void);
|
||||
size_t lgClipboardFiles_testRemoteDatasetCount(void);
|
||||
void lgClipboardFiles_testExpireRemoteDeliveries(void);
|
||||
bool lgClipboardFiles_testBeginRemoteLookup(uint64_t presentation);
|
||||
void lgClipboardFiles_testEndRemoteLookup(uint64_t presentation);
|
||||
bool lgClipboardFiles_testRemoteRead(uint64_t presentation,
|
||||
uint64_t node, uint64_t offset, uint32_t length);
|
||||
void lgClipboardFiles_testForceLocalEof(void);
|
||||
bool lgClipboardFiles_testLocalRequest(
|
||||
const LG_ClipboardFileRequest * request);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -3322,7 +3322,8 @@ static int lg_run(void)
|
||||
frameTimingInit();
|
||||
lgInput_init();
|
||||
lgAudio_init();
|
||||
lgClipboard_init();
|
||||
if (!lgClipboard_init())
|
||||
return -1;
|
||||
|
||||
#ifdef ENABLE_TESTS
|
||||
memset(&l_testCapture, 0, sizeof(l_testCapture));
|
||||
@@ -4018,8 +4019,6 @@ static void lg_shutdown(void)
|
||||
e_startup = NULL;
|
||||
}
|
||||
|
||||
lgClipboard_free();
|
||||
|
||||
app_releaseAllKeybinds();
|
||||
ll_free(g_state.bindings);
|
||||
|
||||
@@ -4029,6 +4028,10 @@ static void lg_shutdown(void)
|
||||
g_state.ds->free();
|
||||
}
|
||||
|
||||
/* Clipboard sources own FUSE dataset references and are released by the
|
||||
* display server before the filesystem is unmounted. */
|
||||
lgClipboard_free();
|
||||
|
||||
// Input callbacks have stopped; the evdev state and overlays can go
|
||||
evdev_free();
|
||||
|
||||
|
||||
@@ -195,6 +195,9 @@ if(ENABLE_WAYLAND)
|
||||
)
|
||||
set(WAYLAND_CLIPBOARD_CASES
|
||||
mime
|
||||
file-import
|
||||
file-source
|
||||
file-replace
|
||||
replace
|
||||
read-eof
|
||||
read-block
|
||||
@@ -241,6 +244,10 @@ if(ENABLE_X11)
|
||||
)
|
||||
set(X11_CLIPBOARD_CASES
|
||||
targets
|
||||
file-import
|
||||
file-incr
|
||||
file-source
|
||||
file-source-active-replace
|
||||
normal
|
||||
incr
|
||||
incr-block
|
||||
@@ -457,6 +464,7 @@ endforeach()
|
||||
|
||||
add_executable(clipboard-tests
|
||||
clipboard_test.c
|
||||
clipboard_files_stub.c
|
||||
../src/clipboard.c
|
||||
)
|
||||
target_compile_definitions(clipboard-tests PRIVATE
|
||||
@@ -473,6 +481,10 @@ target_link_libraries(clipboard-tests
|
||||
)
|
||||
set(CLIPBOARD_CASES
|
||||
preference
|
||||
file-publication
|
||||
file-replacement
|
||||
file-failure
|
||||
callback-serial
|
||||
request
|
||||
remote-local
|
||||
pending-remote
|
||||
@@ -496,8 +508,41 @@ foreach(name IN LISTS CLIPBOARD_CASES)
|
||||
)
|
||||
endforeach()
|
||||
|
||||
add_executable(clipboard-files-tests
|
||||
clipboard_files_test.c
|
||||
../src/clipboard_files.c
|
||||
)
|
||||
target_include_directories(clipboard-files-tests PRIVATE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/../src"
|
||||
)
|
||||
target_link_libraries(clipboard-files-tests
|
||||
${EXE_FLAGS}
|
||||
lg_common
|
||||
PkgConfig::FUSE3
|
||||
)
|
||||
add_test(NAME clipboard-files-lifecycle
|
||||
COMMAND clipboard-files-tests
|
||||
)
|
||||
set_tests_properties(clipboard-files-lifecycle PROPERTIES
|
||||
TIMEOUT 10
|
||||
)
|
||||
add_test(NAME clipboard-files-failures
|
||||
COMMAND clipboard-files-tests failures
|
||||
)
|
||||
set_tests_properties(clipboard-files-failures PROPERTIES
|
||||
TIMEOUT 10
|
||||
)
|
||||
add_test(NAME clipboard-files-mount
|
||||
COMMAND clipboard-files-tests mount
|
||||
)
|
||||
set_tests_properties(clipboard-files-mount PROPERTIES
|
||||
TIMEOUT 10
|
||||
SKIP_RETURN_CODE 77
|
||||
)
|
||||
|
||||
add_executable(spice-clipboard-tests
|
||||
spice_clipboard_test.c
|
||||
clipboard_files_stub.c
|
||||
../src/clipboard.c
|
||||
../transports/SPICE/clipboard.c
|
||||
)
|
||||
|
||||
116
client/tests/clipboard_files_stub.c
Normal file
116
client/tests/clipboard_files_stub.c
Normal file
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2026 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "../src/clipboard_files.h"
|
||||
|
||||
unsigned lgClipboardFilesStubRemoteOfferCount;
|
||||
unsigned lgClipboardFilesStubRemoteClearCount;
|
||||
uint64_t lgClipboardFilesStubRemoteDataset;
|
||||
bool lgClipboardFilesStubRemoteReady;
|
||||
bool lgClipboardFilesStubRemoteOfferResult = true;
|
||||
|
||||
bool lgClipboardFiles_init(void) { return true; }
|
||||
void lgClipboardFiles_free(void) {}
|
||||
bool lgClipboardFiles_setLocal(const char * mime,
|
||||
const void * data, size_t size)
|
||||
{
|
||||
(void)mime; (void)data; (void)size; return false;
|
||||
}
|
||||
void lgClipboardFiles_clearLocal(void) {}
|
||||
bool lgClipboardFiles_getRemote(const char * mime, char ** data, size_t * size)
|
||||
{
|
||||
(void)mime; (void)data; (void)size; return false;
|
||||
}
|
||||
uint64_t lgClipboardFiles_remotePresentationAcquire(void) { return 0; }
|
||||
bool lgClipboardFiles_getRemotePresentation(uint64_t presentation,
|
||||
const char * mime, char ** data, size_t * size)
|
||||
{
|
||||
(void)presentation; (void)mime; (void)data; (void)size; return false;
|
||||
}
|
||||
void lgClipboardFiles_remotePresentationDelivered(uint64_t presentation)
|
||||
{
|
||||
(void)presentation;
|
||||
}
|
||||
void lgClipboardFiles_remotePresentationRelease(uint64_t presentation)
|
||||
{
|
||||
(void)presentation;
|
||||
}
|
||||
bool lgClipboardFiles_remoteReady(uint64_t dataset)
|
||||
{
|
||||
return lgClipboardFilesStubRemoteReady &&
|
||||
dataset == lgClipboardFilesStubRemoteDataset;
|
||||
}
|
||||
bool lgClipboardFiles_remoteOffer(uint64_t dataset)
|
||||
{
|
||||
++lgClipboardFilesStubRemoteOfferCount;
|
||||
lgClipboardFilesStubRemoteDataset = dataset;
|
||||
return lgClipboardFilesStubRemoteOfferResult;
|
||||
}
|
||||
void lgClipboardFiles_remoteClear(void)
|
||||
{
|
||||
++lgClipboardFilesStubRemoteClearCount;
|
||||
lgClipboardFilesStubRemoteDataset = 0;
|
||||
}
|
||||
void lgClipboardFiles_providerUnavailable(void) {}
|
||||
void lgClipboardFiles_remoteAcquired(uint64_t dataset,
|
||||
uint64_t acquisition, LG_ClipboardFileError error)
|
||||
{
|
||||
(void)dataset; (void)acquisition; (void)error;
|
||||
}
|
||||
LG_ClipboardResult lgClipboardFiles_remoteDataBegin(
|
||||
const LG_ClipboardFileRequest * request, uint64_t sizeHint)
|
||||
{
|
||||
(void)request; (void)sizeHint; return LG_CLIPBOARD_RESULT_FAILED;
|
||||
}
|
||||
LG_ClipboardResult lgClipboardFiles_remoteDataChunk(
|
||||
const LG_ClipboardFileRequest * request, uint64_t offset,
|
||||
const void * data, size_t size)
|
||||
{
|
||||
(void)request; (void)offset; (void)data; (void)size;
|
||||
return LG_CLIPBOARD_RESULT_FAILED;
|
||||
}
|
||||
LG_ClipboardResult lgClipboardFiles_remoteDataEnd(
|
||||
const LG_ClipboardFileRequest * request, uint64_t size)
|
||||
{
|
||||
(void)request; (void)size; return LG_CLIPBOARD_RESULT_FAILED;
|
||||
}
|
||||
void lgClipboardFiles_remoteCancel(uint64_t dataset,
|
||||
uint64_t request, LG_ClipboardFileError reason)
|
||||
{
|
||||
(void)dataset; (void)request; (void)reason;
|
||||
}
|
||||
void lgClipboardFiles_localAcquire(uint64_t dataset, uint64_t acquisition)
|
||||
{
|
||||
(void)dataset; (void)acquisition;
|
||||
}
|
||||
void lgClipboardFiles_localRelease(uint64_t dataset, uint64_t acquisition)
|
||||
{
|
||||
(void)dataset; (void)acquisition;
|
||||
}
|
||||
void lgClipboardFiles_localRequest(const LG_ClipboardFileRequest * request)
|
||||
{
|
||||
(void)request;
|
||||
}
|
||||
void lgClipboardFiles_localCancel(uint64_t dataset,
|
||||
uint64_t request, LG_ClipboardFileError reason)
|
||||
{
|
||||
(void)dataset; (void)request; (void)reason;
|
||||
}
|
||||
void lgClipboardFiles_localReady(uint64_t request) { (void)request; }
|
||||
908
client/tests/clipboard_files_test.c
Normal file
908
client/tests/clipboard_files_test.c
Normal file
@@ -0,0 +1,908 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2026 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "../src/clipboard.h"
|
||||
#include "../src/clipboard_files.h"
|
||||
#include "test.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/KVMFRClipboard.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static unsigned notices;
|
||||
static uint64_t datasets[16];
|
||||
static unsigned acquireCount;
|
||||
static uint64_t acquiredDataset;
|
||||
static uint64_t acquiredRequest;
|
||||
static unsigned releaseCount;
|
||||
static uint64_t releasedDataset;
|
||||
static uint64_t releasedAcquisition;
|
||||
static unsigned requestCount;
|
||||
static LG_ClipboardFileRequest remoteRequest;
|
||||
static unsigned remoteReadyCount;
|
||||
static unsigned remoteFailedCount;
|
||||
static uint64_t remoteFailedDataset;
|
||||
static bool remoteRequestSucceeds = true;
|
||||
static uint8_t * responseData;
|
||||
static size_t responseSize;
|
||||
static size_t responseCapacity;
|
||||
static bool responseOpen;
|
||||
static bool responseComplete;
|
||||
static unsigned cancelCount;
|
||||
static uint64_t cancelledDataset;
|
||||
static uint64_t cancelledRequest;
|
||||
static LG_ClipboardFileError cancelledReason;
|
||||
|
||||
void lgClipboard_notifyFiles(uint64_t dataset)
|
||||
{
|
||||
CHECK(notices < sizeof(datasets) / sizeof(datasets[0]));
|
||||
datasets[notices++] = dataset;
|
||||
}
|
||||
|
||||
bool lgClipboard_fileAcquire(uint64_t dataset, uint64_t acquisition)
|
||||
{
|
||||
++acquireCount;
|
||||
acquiredDataset = dataset;
|
||||
acquiredRequest = acquisition;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool lgClipboard_fileAcquired(uint64_t dataset, uint64_t acquisition,
|
||||
LG_ClipboardFileError error)
|
||||
{
|
||||
(void)dataset;
|
||||
(void)acquisition;
|
||||
(void)error;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool lgClipboard_fileRelease(uint64_t dataset, uint64_t acquisition)
|
||||
{
|
||||
++releaseCount;
|
||||
releasedDataset = dataset;
|
||||
releasedAcquisition = acquisition;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool lgClipboard_fileRequest(const LG_ClipboardFileRequest * request)
|
||||
{
|
||||
CHECK(request);
|
||||
++requestCount;
|
||||
remoteRequest = *request;
|
||||
return remoteRequestSucceeds;
|
||||
}
|
||||
|
||||
LG_ClipboardResult lgClipboard_fileDataBegin(
|
||||
const LG_ClipboardFileRequest * request, uint64_t sizeHint)
|
||||
{
|
||||
CHECK(request);
|
||||
(void)sizeHint;
|
||||
responseSize = 0;
|
||||
responseOpen = true;
|
||||
responseComplete = false;
|
||||
return LG_CLIPBOARD_RESULT_ACCEPTED;
|
||||
}
|
||||
|
||||
LG_ClipboardResult lgClipboard_fileDataChunk(
|
||||
const LG_ClipboardFileRequest * request, uint64_t responseOffset,
|
||||
const void * data, size_t size)
|
||||
{
|
||||
CHECK(request);
|
||||
CHECK(responseOpen);
|
||||
CHECK(responseOffset == responseSize);
|
||||
CHECK(data || !size);
|
||||
CHECK(size <= SIZE_MAX - responseSize);
|
||||
const size_t wanted = responseSize + size;
|
||||
if (wanted > responseCapacity)
|
||||
{
|
||||
uint8_t * resized = realloc(responseData, wanted);
|
||||
CHECK(resized);
|
||||
responseData = resized;
|
||||
responseCapacity = wanted;
|
||||
}
|
||||
if (size)
|
||||
memcpy(responseData + responseSize, data, size);
|
||||
responseSize = wanted;
|
||||
return LG_CLIPBOARD_RESULT_ACCEPTED;
|
||||
}
|
||||
|
||||
LG_ClipboardResult lgClipboard_fileDataEnd(
|
||||
const LG_ClipboardFileRequest * request, uint64_t finalSize)
|
||||
{
|
||||
CHECK(request);
|
||||
CHECK(responseOpen);
|
||||
CHECK(finalSize == responseSize);
|
||||
responseOpen = false;
|
||||
responseComplete = true;
|
||||
return LG_CLIPBOARD_RESULT_ACCEPTED;
|
||||
}
|
||||
|
||||
bool lgClipboard_fileCancel(uint64_t dataset, uint64_t request,
|
||||
LG_ClipboardFileError reason)
|
||||
{
|
||||
++cancelCount;
|
||||
cancelledDataset = dataset;
|
||||
cancelledRequest = request;
|
||||
cancelledReason = reason;
|
||||
return true;
|
||||
}
|
||||
|
||||
void lgClipboard_fileRemoteReady(uint64_t dataset)
|
||||
{
|
||||
CHECK(dataset == acquiredDataset);
|
||||
++remoteReadyCount;
|
||||
}
|
||||
|
||||
void lgClipboard_fileRemoteFailed(uint64_t dataset)
|
||||
{
|
||||
CHECK(dataset);
|
||||
++remoteFailedCount;
|
||||
remoteFailedDataset = dataset;
|
||||
}
|
||||
|
||||
static bool runLocalRequest(uint64_t dataset, uint64_t node,
|
||||
LG_ClipboardFileOperation operation, uint64_t offset, uint32_t length)
|
||||
{
|
||||
static uint64_t requestId = 1;
|
||||
responseSize = 0;
|
||||
responseOpen = false;
|
||||
responseComplete = false;
|
||||
cancelCount = 0;
|
||||
cancelledDataset = 0;
|
||||
cancelledRequest = 0;
|
||||
cancelledReason = LG_CLIPBOARD_FILE_ERROR_NONE;
|
||||
const LG_ClipboardFileRequest request =
|
||||
{
|
||||
.dataset = dataset,
|
||||
.request = requestId++,
|
||||
.node = node,
|
||||
.offset = offset,
|
||||
.length = length,
|
||||
.operation = operation,
|
||||
};
|
||||
const bool result = lgClipboardFiles_testLocalRequest(&request);
|
||||
CHECK(!result || responseComplete);
|
||||
CHECK(result ? cancelCount == 0U : cancelCount == 1U);
|
||||
if (!result)
|
||||
{
|
||||
CHECK(cancelledDataset == dataset);
|
||||
CHECK(cancelledRequest == request.request);
|
||||
CHECK(cancelledReason != LG_CLIPBOARD_FILE_ERROR_NONE);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool responseEntry(const char * name, KVMFRClipboardFileEntry * result)
|
||||
{
|
||||
size_t offset = 0;
|
||||
while (offset < responseSize)
|
||||
{
|
||||
CHECK(responseSize - offset >= sizeof(*result));
|
||||
KVMFRClipboardFileEntry entry;
|
||||
memcpy(&entry, responseData + offset, sizeof(entry));
|
||||
const uint64_t bytes = KVMFR_CLIPBOARD_FILE_ENTRY_BYTES(
|
||||
entry.nameLength);
|
||||
CHECK(bytes <= SIZE_MAX);
|
||||
CHECK((size_t)bytes <= responseSize - offset);
|
||||
const char * entryName = (const char *)responseData + offset +
|
||||
sizeof(entry);
|
||||
if (strlen(name) == entry.nameLength &&
|
||||
!memcmp(entryName, name, entry.nameLength))
|
||||
{
|
||||
*result = entry;
|
||||
return true;
|
||||
}
|
||||
offset += (size_t)bytes;
|
||||
}
|
||||
CHECK(offset == responseSize);
|
||||
return false;
|
||||
}
|
||||
|
||||
static void createFile(const char * path, const char * data)
|
||||
{
|
||||
const int fd = open(path,
|
||||
O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0600);
|
||||
CHECK(fd >= 0);
|
||||
const size_t length = strlen(data);
|
||||
CHECK(write(fd, data, length) == (ssize_t)length);
|
||||
CHECK(close(fd) == 0);
|
||||
}
|
||||
|
||||
static void testLocalLifecycle(void)
|
||||
{
|
||||
char directory[] = "/tmp/lg-clipboard-files-XXXXXX";
|
||||
CHECK(mkdtemp(directory));
|
||||
char path[256];
|
||||
CHECK(snprintf(path, sizeof(path), "%s/item.txt", directory) > 0);
|
||||
const int fd = open(path, O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0600);
|
||||
CHECK(fd >= 0);
|
||||
CHECK(write(fd, "clipboard", 9) == 9);
|
||||
CHECK(close(fd) == 0);
|
||||
|
||||
CHECK(lgClipboardFiles_testInit(UINT64_C(0x1234000012340000)));
|
||||
char uri[320];
|
||||
const int uriLength = snprintf(uri, sizeof(uri), "file://%s\r\n", path);
|
||||
CHECK(uriLength > 0 && (size_t)uriLength < sizeof(uri));
|
||||
CHECK(lgClipboardFiles_setLocal(
|
||||
"text/uri-list", uri, (size_t)uriLength));
|
||||
CHECK(notices == 1);
|
||||
CHECK(datasets[0] != 0);
|
||||
CHECK(kvmfrClipboardTransferFromClient(datasets[0]));
|
||||
|
||||
lgClipboardFiles_clearLocal();
|
||||
char gnome[336];
|
||||
const int gnomeLength = snprintf(
|
||||
gnome, sizeof(gnome), "copy\nfile://%s\n", path);
|
||||
CHECK(gnomeLength > 0 && (size_t)gnomeLength < sizeof(gnome));
|
||||
CHECK(lgClipboardFiles_setLocal("x-special/gnome-copied-files",
|
||||
gnome, (size_t)gnomeLength));
|
||||
CHECK(notices == 2);
|
||||
CHECK(datasets[1] != datasets[0]);
|
||||
|
||||
static const char invalid[] = "file:///does/not/exist\n";
|
||||
CHECK(!lgClipboardFiles_setLocal(
|
||||
"text/uri-list", invalid, sizeof(invalid) - 1U));
|
||||
lgClipboardFiles_clearLocal();
|
||||
lgClipboardFiles_free();
|
||||
lgClipboardFiles_free();
|
||||
|
||||
CHECK(lgClipboardFiles_testInit(UINT64_C(0x5678000056780000)));
|
||||
CHECK(lgClipboardFiles_setLocal(
|
||||
"text/uri-list", uri, (size_t)uriLength));
|
||||
CHECK(notices == 3);
|
||||
CHECK(datasets[2] != datasets[0]);
|
||||
CHECK(datasets[2] != datasets[1]);
|
||||
lgClipboardFiles_free();
|
||||
|
||||
CHECK(unlink(path) == 0);
|
||||
CHECK(rmdir(directory) == 0);
|
||||
}
|
||||
|
||||
static void testLocalUriValidation(void)
|
||||
{
|
||||
char directory[] = "/tmp/lg-clipboard-uri-XXXXXX";
|
||||
CHECK(mkdtemp(directory));
|
||||
|
||||
char plain[256];
|
||||
char spaced[256];
|
||||
char link[256];
|
||||
char alias[256];
|
||||
CHECK(snprintf(plain, sizeof(plain), "%s/item.txt", directory) > 0);
|
||||
CHECK(snprintf(spaced, sizeof(spaced),
|
||||
"%s/item space.txt", directory) > 0);
|
||||
CHECK(snprintf(link, sizeof(link), "%s/link.txt", directory) > 0);
|
||||
CHECK(snprintf(alias, sizeof(alias), "%s/alias", directory) > 0);
|
||||
createFile(plain, "plain");
|
||||
createFile(spaced, "spaced");
|
||||
CHECK(symlink("item.txt", link) == 0);
|
||||
CHECK(symlink(".", alias) == 0);
|
||||
|
||||
CHECK(lgClipboardFiles_testInit(UINT64_C(0x684b2452684b2452)));
|
||||
const unsigned before = notices;
|
||||
char uri[512];
|
||||
int length = snprintf(uri, sizeof(uri),
|
||||
"FILE://LOCALHOST%s/item%%20space.txt\r\n", directory);
|
||||
CHECK(length > 0 && (size_t)length < sizeof(uri));
|
||||
CHECK(lgClipboardFiles_setLocal(
|
||||
"text/uri-list", uri, (size_t)length));
|
||||
lgClipboardFiles_clearLocal();
|
||||
|
||||
length = snprintf(uri, sizeof(uri), "file://%s%%\n", plain);
|
||||
CHECK(length > 0 && (size_t)length < sizeof(uri));
|
||||
CHECK(!lgClipboardFiles_setLocal(
|
||||
"text/uri-list", uri, (size_t)length));
|
||||
length = snprintf(uri, sizeof(uri), "file://%s%%0\n", plain);
|
||||
CHECK(length > 0 && (size_t)length < sizeof(uri));
|
||||
CHECK(!lgClipboardFiles_setLocal(
|
||||
"text/uri-list", uri, (size_t)length));
|
||||
length = snprintf(uri, sizeof(uri), "file://%s%%GG\n", plain);
|
||||
CHECK(length > 0 && (size_t)length < sizeof(uri));
|
||||
CHECK(!lgClipboardFiles_setLocal(
|
||||
"text/uri-list", uri, (size_t)length));
|
||||
length = snprintf(uri, sizeof(uri), "file://%s%%00suffix\n", plain);
|
||||
CHECK(length > 0 && (size_t)length < sizeof(uri));
|
||||
CHECK(!lgClipboardFiles_setLocal(
|
||||
"text/uri-list", uri, (size_t)length));
|
||||
length = snprintf(uri, sizeof(uri), "file://%s?query\n", plain);
|
||||
CHECK(length > 0 && (size_t)length < sizeof(uri));
|
||||
CHECK(!lgClipboardFiles_setLocal(
|
||||
"text/uri-list", uri, (size_t)length));
|
||||
length = snprintf(uri, sizeof(uri), "file://%s#fragment\n", plain);
|
||||
CHECK(length > 0 && (size_t)length < sizeof(uri));
|
||||
CHECK(!lgClipboardFiles_setLocal(
|
||||
"text/uri-list", uri, (size_t)length));
|
||||
length = snprintf(uri, sizeof(uri), "file://example.invalid%s\n", plain);
|
||||
CHECK(length > 0 && (size_t)length < sizeof(uri));
|
||||
CHECK(!lgClipboardFiles_setLocal(
|
||||
"text/uri-list", uri, (size_t)length));
|
||||
static const char root[] = "file:///\n";
|
||||
CHECK(!lgClipboardFiles_setLocal(
|
||||
"text/uri-list", root, sizeof(root) - 1U));
|
||||
length = snprintf(uri, sizeof(uri), "file://%s/\n", directory);
|
||||
CHECK(length > 0 && (size_t)length < sizeof(uri));
|
||||
CHECK(lgClipboardFiles_setLocal(
|
||||
"text/uri-list", uri, (size_t)length));
|
||||
lgClipboardFiles_clearLocal();
|
||||
length = snprintf(uri, sizeof(uri), "file://%s/./item.txt\n", directory);
|
||||
CHECK(length > 0 && (size_t)length < sizeof(uri));
|
||||
CHECK(!lgClipboardFiles_setLocal(
|
||||
"text/uri-list", uri, (size_t)length));
|
||||
length = snprintf(uri, sizeof(uri), "file://%s//item.txt\n", directory);
|
||||
CHECK(length > 0 && (size_t)length < sizeof(uri));
|
||||
CHECK(!lgClipboardFiles_setLocal(
|
||||
"text/uri-list", uri, (size_t)length));
|
||||
length = snprintf(uri, sizeof(uri), "file://%s\n", link);
|
||||
CHECK(length > 0 && (size_t)length < sizeof(uri));
|
||||
CHECK(!lgClipboardFiles_setLocal(
|
||||
"text/uri-list", uri, (size_t)length));
|
||||
length = snprintf(uri, sizeof(uri),
|
||||
"file://%s/alias/item.txt\n", directory);
|
||||
CHECK(length > 0 && (size_t)length < sizeof(uri));
|
||||
CHECK(!lgClipboardFiles_setLocal(
|
||||
"text/uri-list", uri, (size_t)length));
|
||||
length = snprintf(uri, sizeof(uri), "file://%s\n", spaced);
|
||||
CHECK(length > 0 && (size_t)length < sizeof(uri));
|
||||
CHECK(!lgClipboardFiles_setLocal(
|
||||
"text/uri-list", uri, (size_t)length));
|
||||
length = snprintf(uri, sizeof(uri),
|
||||
"cutx\nfile://%s\n", plain);
|
||||
CHECK(length > 0 && (size_t)length < sizeof(uri));
|
||||
CHECK(!lgClipboardFiles_setLocal(
|
||||
"x-special/gnome-copied-files", uri, (size_t)length));
|
||||
CHECK(notices == before + 2U);
|
||||
|
||||
lgClipboardFiles_free();
|
||||
CHECK(unlink(alias) == 0);
|
||||
CHECK(unlink(link) == 0);
|
||||
CHECK(unlink(spaced) == 0);
|
||||
CHECK(unlink(plain) == 0);
|
||||
CHECK(rmdir(directory) == 0);
|
||||
}
|
||||
|
||||
static void testLocalSnapshot(void)
|
||||
{
|
||||
char directory[] = "/tmp/lg-clipboard-snapshot-XXXXXX";
|
||||
CHECK(mkdtemp(directory));
|
||||
char stable[256];
|
||||
char changed[256];
|
||||
CHECK(snprintf(stable, sizeof(stable),
|
||||
"%s/stable.txt", directory) > 0);
|
||||
CHECK(snprintf(changed, sizeof(changed),
|
||||
"%s/changed.txt", directory) > 0);
|
||||
createFile(stable, "stable");
|
||||
createFile(changed, "before");
|
||||
|
||||
CHECK(lgClipboardFiles_testInit(UINT64_C(0x1a72021b1a72021b)));
|
||||
char uri[320];
|
||||
const int uriLength = snprintf(
|
||||
uri, sizeof(uri), "file://%s\r\n", directory);
|
||||
CHECK(uriLength > 0 && (size_t)uriLength < sizeof(uri));
|
||||
CHECK(lgClipboardFiles_setLocal(
|
||||
"text/uri-list", uri, (size_t)uriLength));
|
||||
const uint64_t dataset = datasets[notices - 1U];
|
||||
|
||||
CHECK(runLocalRequest(dataset, KVMFR_CLIPBOARD_FILE_ROOT_NODE,
|
||||
LG_CLIPBOARD_FILE_LIST, 0, 0));
|
||||
const char * base = strrchr(directory, '/');
|
||||
CHECK(base && base[1]);
|
||||
KVMFRClipboardFileEntry rootEntry;
|
||||
CHECK(responseEntry(base + 1, &rootEntry));
|
||||
CHECK(rootEntry.type == KVMFR_CLIPBOARD_FILE_TYPE_DIRECTORY);
|
||||
|
||||
CHECK(runLocalRequest(dataset, rootEntry.node,
|
||||
LG_CLIPBOARD_FILE_LIST, 0, 0));
|
||||
KVMFRClipboardFileEntry stableEntry;
|
||||
KVMFRClipboardFileEntry changedEntry;
|
||||
CHECK(responseEntry("stable.txt", &stableEntry));
|
||||
CHECK(responseEntry("changed.txt", &changedEntry));
|
||||
|
||||
char moved[320];
|
||||
CHECK(snprintf(moved, sizeof(moved), "%s-moved", directory) > 0);
|
||||
CHECK(rename(directory, moved) == 0);
|
||||
CHECK(mkdir(directory, 0700) == 0);
|
||||
char replacement[384];
|
||||
CHECK(snprintf(replacement, sizeof(replacement),
|
||||
"%s/stable.txt", directory) > 0);
|
||||
createFile(replacement, "replacement");
|
||||
|
||||
CHECK(runLocalRequest(dataset, stableEntry.node,
|
||||
LG_CLIPBOARD_FILE_READ, 0, (uint32_t)stableEntry.size));
|
||||
CHECK(responseSize == strlen("stable"));
|
||||
CHECK(!memcmp(responseData, "stable", responseSize));
|
||||
lgClipboardFiles_testForceLocalEof();
|
||||
CHECK(!runLocalRequest(dataset, stableEntry.node,
|
||||
LG_CLIPBOARD_FILE_READ, 0, (uint32_t)stableEntry.size));
|
||||
CHECK(cancelledReason == LG_CLIPBOARD_FILE_ERROR_IO);
|
||||
|
||||
char movedChanged[384];
|
||||
CHECK(snprintf(movedChanged, sizeof(movedChanged),
|
||||
"%s/changed.txt", moved) > 0);
|
||||
const int changedFd = open(movedChanged,
|
||||
O_WRONLY | O_APPEND | O_CLOEXEC);
|
||||
CHECK(changedFd >= 0);
|
||||
CHECK(write(changedFd, "-modified", 9) == 9);
|
||||
CHECK(close(changedFd) == 0);
|
||||
CHECK(!runLocalRequest(dataset, changedEntry.node,
|
||||
LG_CLIPBOARD_FILE_READ, 0, 64));
|
||||
CHECK(cancelledReason == LG_CLIPBOARD_FILE_ERROR_INVALID);
|
||||
CHECK(!runLocalRequest(dataset, changedEntry.node,
|
||||
LG_CLIPBOARD_FILE_READ, 0, (uint32_t)changedEntry.size));
|
||||
CHECK(cancelledReason == LG_CLIPBOARD_FILE_ERROR_STALE);
|
||||
|
||||
char movedStable[384];
|
||||
char oldStable[384];
|
||||
CHECK(snprintf(movedStable, sizeof(movedStable),
|
||||
"%s/stable.txt", moved) > 0);
|
||||
CHECK(snprintf(oldStable, sizeof(oldStable),
|
||||
"%s/stable.old", moved) > 0);
|
||||
CHECK(rename(movedStable, oldStable) == 0);
|
||||
createFile(movedStable, "stable");
|
||||
CHECK(!runLocalRequest(dataset, stableEntry.node,
|
||||
LG_CLIPBOARD_FILE_READ, 0, 64));
|
||||
CHECK(cancelledReason == LG_CLIPBOARD_FILE_ERROR_INVALID);
|
||||
CHECK(!runLocalRequest(dataset, stableEntry.node,
|
||||
LG_CLIPBOARD_FILE_READ, 0, (uint32_t)stableEntry.size));
|
||||
CHECK(cancelledReason == LG_CLIPBOARD_FILE_ERROR_STALE);
|
||||
|
||||
lgClipboardFiles_free();
|
||||
CHECK(unlink(replacement) == 0);
|
||||
CHECK(rmdir(directory) == 0);
|
||||
CHECK(unlink(movedStable) == 0);
|
||||
CHECK(unlink(oldStable) == 0);
|
||||
CHECK(unlink(movedChanged) == 0);
|
||||
CHECK(rmdir(moved) == 0);
|
||||
}
|
||||
|
||||
static void testLocalUnsupportedEntry(void)
|
||||
{
|
||||
char directory[] = "/tmp/lg-clipboard-unsupported-XXXXXX";
|
||||
CHECK(mkdtemp(directory));
|
||||
char target[256];
|
||||
char link[256];
|
||||
char fifo[256];
|
||||
CHECK(snprintf(target, sizeof(target),
|
||||
"%s/target.txt", directory) > 0);
|
||||
CHECK(snprintf(link, sizeof(link), "%s/link.txt", directory) > 0);
|
||||
CHECK(snprintf(fifo, sizeof(fifo), "%s/special", directory) > 0);
|
||||
createFile(target, "target");
|
||||
CHECK(symlink("target.txt", link) == 0);
|
||||
CHECK(mkfifo(fifo, 0600) == 0);
|
||||
|
||||
CHECK(lgClipboardFiles_testInit(UINT64_C(0x62d1147362d11473)));
|
||||
char uri[320];
|
||||
const int uriLength = snprintf(
|
||||
uri, sizeof(uri), "file://%s\r\n", directory);
|
||||
CHECK(uriLength > 0 && (size_t)uriLength < sizeof(uri));
|
||||
CHECK(lgClipboardFiles_setLocal(
|
||||
"text/uri-list", uri, (size_t)uriLength));
|
||||
const uint64_t dataset = datasets[notices - 1U];
|
||||
CHECK(runLocalRequest(dataset, KVMFR_CLIPBOARD_FILE_ROOT_NODE,
|
||||
LG_CLIPBOARD_FILE_LIST, 0, 0));
|
||||
const char * base = strrchr(directory, '/');
|
||||
CHECK(base && base[1]);
|
||||
KVMFRClipboardFileEntry rootEntry;
|
||||
CHECK(responseEntry(base + 1, &rootEntry));
|
||||
CHECK(!runLocalRequest(dataset, rootEntry.node,
|
||||
LG_CLIPBOARD_FILE_LIST, 0, 0));
|
||||
CHECK(cancelledReason == LG_CLIPBOARD_FILE_ERROR_NOT_SUPPORTED);
|
||||
|
||||
lgClipboardFiles_free();
|
||||
CHECK(unlink(fifo) == 0);
|
||||
CHECK(unlink(link) == 0);
|
||||
CHECK(unlink(target) == 0);
|
||||
CHECK(rmdir(directory) == 0);
|
||||
}
|
||||
|
||||
static uint64_t completeRemoteOffer(uint64_t dataset, const char * name)
|
||||
{
|
||||
const unsigned previousAcquires = acquireCount;
|
||||
const unsigned previousRequests = requestCount;
|
||||
const unsigned previousReady = remoteReadyCount;
|
||||
CHECK(lgClipboardFiles_remoteOffer(dataset));
|
||||
CHECK(acquireCount == previousAcquires + 1U);
|
||||
CHECK(acquiredDataset == dataset);
|
||||
const uint64_t acquisition = acquiredRequest;
|
||||
|
||||
lgClipboardFiles_remoteAcquired(dataset, acquisition,
|
||||
LG_CLIPBOARD_FILE_ERROR_NONE);
|
||||
CHECK(requestCount == previousRequests + 1U);
|
||||
CHECK(remoteRequest.dataset == dataset);
|
||||
CHECK(remoteRequest.node == KVMFR_CLIPBOARD_FILE_ROOT_NODE);
|
||||
CHECK(remoteRequest.operation == LG_CLIPBOARD_FILE_LIST);
|
||||
|
||||
const size_t nameLength = strlen(name);
|
||||
const uint64_t wireSize = KVMFR_CLIPBOARD_FILE_ENTRY_BYTES(nameLength);
|
||||
CHECK(wireSize <= SIZE_MAX);
|
||||
uint8_t * wire = calloc(1, (size_t)wireSize);
|
||||
CHECK(wire);
|
||||
const KVMFRClipboardFileEntry entry =
|
||||
{
|
||||
.node = 2,
|
||||
.size = 9,
|
||||
.createdNs = 1,
|
||||
.modifiedNs = 2,
|
||||
.type = KVMFR_CLIPBOARD_FILE_TYPE_REGULAR,
|
||||
.nameLength = (uint16_t)nameLength,
|
||||
};
|
||||
memcpy(wire, &entry, sizeof(entry));
|
||||
memcpy(wire + sizeof(entry), name, nameLength);
|
||||
CHECK(lgClipboardFiles_remoteDataBegin(
|
||||
&remoteRequest, wireSize) == LG_CLIPBOARD_RESULT_ACCEPTED);
|
||||
CHECK(lgClipboardFiles_remoteDataChunk(&remoteRequest, 0,
|
||||
wire, (size_t)wireSize) == LG_CLIPBOARD_RESULT_ACCEPTED);
|
||||
CHECK(lgClipboardFiles_remoteDataEnd(
|
||||
&remoteRequest, wireSize) == LG_CLIPBOARD_RESULT_ACCEPTED);
|
||||
free(wire);
|
||||
CHECK(remoteReadyCount == previousReady + 1U);
|
||||
CHECK(lgClipboardFiles_remoteReady(dataset));
|
||||
return acquisition;
|
||||
}
|
||||
|
||||
static uint64_t beginRemoteOffer(uint64_t dataset)
|
||||
{
|
||||
const unsigned previousAcquires = acquireCount;
|
||||
CHECK(lgClipboardFiles_remoteOffer(dataset));
|
||||
CHECK(acquireCount == previousAcquires + 1U);
|
||||
CHECK(acquiredDataset == dataset);
|
||||
return acquiredRequest;
|
||||
}
|
||||
|
||||
static LG_ClipboardFileRequest beginRemoteList(uint64_t dataset,
|
||||
uint64_t * acquisition)
|
||||
{
|
||||
const unsigned previousRequests = requestCount;
|
||||
*acquisition = beginRemoteOffer(dataset);
|
||||
lgClipboardFiles_remoteAcquired(dataset, *acquisition,
|
||||
LG_CLIPBOARD_FILE_ERROR_NONE);
|
||||
CHECK(requestCount == previousRequests + 1U);
|
||||
CHECK(remoteRequest.dataset == dataset);
|
||||
CHECK(remoteRequest.operation == LG_CLIPBOARD_FILE_LIST);
|
||||
return remoteRequest;
|
||||
}
|
||||
|
||||
static void checkRemoteFailure(uint64_t dataset,
|
||||
unsigned previousFailures, unsigned previousReleases,
|
||||
bool acquired)
|
||||
{
|
||||
CHECK(remoteFailedCount == previousFailures + 1U);
|
||||
CHECK(remoteFailedDataset == dataset);
|
||||
CHECK(releaseCount == previousReleases + (acquired ? 1U : 0U));
|
||||
if (acquired)
|
||||
CHECK(releasedDataset == dataset);
|
||||
CHECK(lgClipboardFiles_testRemoteDatasetCount() == 0U);
|
||||
}
|
||||
|
||||
static void testRemoteFailures(void)
|
||||
{
|
||||
CHECK(lgClipboardFiles_testInit(UINT64_C(0x7315000073150000)));
|
||||
acquireCount = 0;
|
||||
releaseCount = 0;
|
||||
requestCount = 0;
|
||||
remoteFailedCount = 0;
|
||||
remoteFailedDataset = 0;
|
||||
remoteRequestSucceeds = true;
|
||||
|
||||
uint64_t dataset = KVMFR_CLIPBOARD_TRANSFER_HELPER |
|
||||
UINT64_C(0x73150001);
|
||||
uint64_t acquisition = beginRemoteOffer(dataset);
|
||||
unsigned previousFailures = remoteFailedCount;
|
||||
unsigned previousReleases = releaseCount;
|
||||
lgClipboardFiles_remoteAcquired(dataset, acquisition,
|
||||
LG_CLIPBOARD_FILE_ERROR_ACCESS);
|
||||
checkRemoteFailure(dataset, previousFailures, previousReleases, false);
|
||||
|
||||
dataset += 1U;
|
||||
acquisition = beginRemoteOffer(dataset);
|
||||
previousFailures = remoteFailedCount;
|
||||
previousReleases = releaseCount;
|
||||
lgClipboardFiles_remoteCancel(dataset, acquisition,
|
||||
LG_CLIPBOARD_FILE_ERROR_CANCELLED);
|
||||
checkRemoteFailure(dataset, previousFailures, previousReleases, false);
|
||||
|
||||
dataset += 1U;
|
||||
LG_ClipboardFileRequest request = beginRemoteList(dataset, &acquisition);
|
||||
previousFailures = remoteFailedCount;
|
||||
previousReleases = releaseCount;
|
||||
lgClipboardFiles_remoteCancel(dataset, request.request,
|
||||
LG_CLIPBOARD_FILE_ERROR_CANCELLED);
|
||||
checkRemoteFailure(dataset, previousFailures, previousReleases, true);
|
||||
|
||||
dataset += 1U;
|
||||
acquisition = beginRemoteOffer(dataset);
|
||||
previousFailures = remoteFailedCount;
|
||||
previousReleases = releaseCount;
|
||||
remoteRequestSucceeds = false;
|
||||
lgClipboardFiles_remoteAcquired(dataset, acquisition,
|
||||
LG_CLIPBOARD_FILE_ERROR_NONE);
|
||||
remoteRequestSucceeds = true;
|
||||
checkRemoteFailure(dataset, previousFailures, previousReleases, true);
|
||||
|
||||
dataset += 1U;
|
||||
request = beginRemoteList(dataset, &acquisition);
|
||||
LG_ClipboardFileRequest malformed = request;
|
||||
malformed.node += 1U;
|
||||
previousFailures = remoteFailedCount;
|
||||
previousReleases = releaseCount;
|
||||
CHECK(lgClipboardFiles_remoteDataBegin(&malformed, 0) ==
|
||||
LG_CLIPBOARD_RESULT_FAILED);
|
||||
checkRemoteFailure(dataset, previousFailures, previousReleases, true);
|
||||
|
||||
dataset += 1U;
|
||||
request = beginRemoteList(dataset, &acquisition);
|
||||
CHECK(lgClipboardFiles_remoteDataBegin(&request, 1) ==
|
||||
LG_CLIPBOARD_RESULT_ACCEPTED);
|
||||
previousFailures = remoteFailedCount;
|
||||
previousReleases = releaseCount;
|
||||
CHECK(lgClipboardFiles_remoteDataChunk(&request, 0, NULL, 1) ==
|
||||
LG_CLIPBOARD_RESULT_FAILED);
|
||||
checkRemoteFailure(dataset, previousFailures, previousReleases, true);
|
||||
|
||||
dataset += 1U;
|
||||
request = beginRemoteList(dataset, &acquisition);
|
||||
CHECK(lgClipboardFiles_remoteDataBegin(&request, 1) ==
|
||||
LG_CLIPBOARD_RESULT_ACCEPTED);
|
||||
previousFailures = remoteFailedCount;
|
||||
previousReleases = releaseCount;
|
||||
CHECK(lgClipboardFiles_remoteDataEnd(&request, 0) ==
|
||||
LG_CLIPBOARD_RESULT_FAILED);
|
||||
checkRemoteFailure(dataset, previousFailures, previousReleases, true);
|
||||
|
||||
dataset += 1U;
|
||||
request = beginRemoteList(dataset, &acquisition);
|
||||
static const char invalidName[] = "bad/name";
|
||||
const size_t wireSize = (size_t)KVMFR_CLIPBOARD_FILE_ENTRY_BYTES(
|
||||
sizeof(invalidName) - 1U);
|
||||
uint8_t * wire = calloc(1, wireSize);
|
||||
CHECK(wire);
|
||||
const KVMFRClipboardFileEntry entry =
|
||||
{
|
||||
.node = 2,
|
||||
.size = 1,
|
||||
.type = KVMFR_CLIPBOARD_FILE_TYPE_REGULAR,
|
||||
.nameLength = sizeof(invalidName) - 1U,
|
||||
};
|
||||
memcpy(wire, &entry, sizeof(entry));
|
||||
memcpy(wire + sizeof(entry), invalidName, sizeof(invalidName) - 1U);
|
||||
CHECK(lgClipboardFiles_remoteDataBegin(&request, wireSize) ==
|
||||
LG_CLIPBOARD_RESULT_ACCEPTED);
|
||||
CHECK(lgClipboardFiles_remoteDataChunk(&request, 0, wire, wireSize) ==
|
||||
LG_CLIPBOARD_RESULT_ACCEPTED);
|
||||
previousFailures = remoteFailedCount;
|
||||
previousReleases = releaseCount;
|
||||
CHECK(lgClipboardFiles_remoteDataEnd(&request, wireSize) ==
|
||||
LG_CLIPBOARD_RESULT_ACCEPTED);
|
||||
free(wire);
|
||||
checkRemoteFailure(dataset, previousFailures, previousReleases, true);
|
||||
|
||||
lgClipboardFiles_free();
|
||||
}
|
||||
|
||||
static void checkRemoteUri(uint64_t presentation, const char * name)
|
||||
{
|
||||
char * uri = NULL;
|
||||
size_t size = 0;
|
||||
CHECK(lgClipboardFiles_getRemotePresentation(
|
||||
presentation, "text/uri-list", &uri, &size));
|
||||
CHECK(uri);
|
||||
CHECK(size == strlen(uri));
|
||||
CHECK(strstr(uri, name));
|
||||
free(uri);
|
||||
}
|
||||
|
||||
typedef struct ReleasePresentationTask
|
||||
{
|
||||
pthread_barrier_t * barrier;
|
||||
uint64_t presentation;
|
||||
}
|
||||
ReleasePresentationTask;
|
||||
|
||||
static void * releasePresentation(void * opaque)
|
||||
{
|
||||
ReleasePresentationTask * task = opaque;
|
||||
const int result = pthread_barrier_wait(task->barrier);
|
||||
CHECK(result == 0 || result == PTHREAD_BARRIER_SERIAL_THREAD);
|
||||
lgClipboardFiles_remotePresentationRelease(task->presentation);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void releasePresentationConcurrently(uint64_t presentation)
|
||||
{
|
||||
pthread_barrier_t barrier;
|
||||
CHECK(pthread_barrier_init(&barrier, NULL, 3) == 0);
|
||||
ReleasePresentationTask task =
|
||||
{
|
||||
.barrier = &barrier,
|
||||
.presentation = presentation,
|
||||
};
|
||||
pthread_t first;
|
||||
pthread_t second;
|
||||
CHECK(pthread_create(&first, NULL, releasePresentation, &task) == 0);
|
||||
CHECK(pthread_create(&second, NULL, releasePresentation, &task) == 0);
|
||||
const int result = pthread_barrier_wait(&barrier);
|
||||
CHECK(result == 0 || result == PTHREAD_BARRIER_SERIAL_THREAD);
|
||||
CHECK(pthread_join(first, NULL) == 0);
|
||||
CHECK(pthread_join(second, NULL) == 0);
|
||||
CHECK(pthread_barrier_destroy(&barrier) == 0);
|
||||
}
|
||||
|
||||
static void testRemoteLifecycle(void)
|
||||
{
|
||||
CHECK(lgClipboardFiles_testFuseStopWake());
|
||||
CHECK(lgClipboardFiles_testInit(UINT64_C(0x7311000073110000)));
|
||||
CHECK(lgClipboardFiles_testUnsentRemoteOwnership());
|
||||
acquireCount = 0;
|
||||
releaseCount = 0;
|
||||
requestCount = 0;
|
||||
remoteReadyCount = 0;
|
||||
cancelCount = 0;
|
||||
|
||||
const uint64_t firstDataset = KVMFR_CLIPBOARD_TRANSFER_HELPER |
|
||||
UINT64_C(0x73110001);
|
||||
const uint64_t first = completeRemoteOffer(firstDataset, "first.txt");
|
||||
|
||||
CHECK(lgClipboardFiles_testRemoteRead(first, 2, 0, 9));
|
||||
LG_ClipboardFileRequest shortRead = remoteRequest;
|
||||
CHECK(lgClipboardFiles_remoteDataBegin(&shortRead, 8) ==
|
||||
LG_CLIPBOARD_RESULT_FAILED);
|
||||
|
||||
CHECK(lgClipboardFiles_testRemoteRead(first, 2, 0, 9));
|
||||
shortRead = remoteRequest;
|
||||
CHECK(lgClipboardFiles_remoteDataBegin(&shortRead, 9) ==
|
||||
LG_CLIPBOARD_RESULT_ACCEPTED);
|
||||
const uint8_t shortData[8] = { 0 };
|
||||
CHECK(lgClipboardFiles_remoteDataChunk(&shortRead, 0,
|
||||
shortData, sizeof(shortData)) == LG_CLIPBOARD_RESULT_ACCEPTED);
|
||||
CHECK(lgClipboardFiles_remoteDataEnd(&shortRead, sizeof(shortData)) ==
|
||||
LG_CLIPBOARD_RESULT_FAILED);
|
||||
|
||||
const uint64_t firstPresentation =
|
||||
lgClipboardFiles_remotePresentationAcquire();
|
||||
CHECK(firstPresentation == first);
|
||||
checkRemoteUri(firstPresentation, "first.txt");
|
||||
|
||||
const uint64_t secondDataset = KVMFR_CLIPBOARD_TRANSFER_HELPER |
|
||||
UINT64_C(0x73110002);
|
||||
completeRemoteOffer(secondDataset, "second.txt");
|
||||
CHECK(lgClipboardFiles_testRemoteDatasetCount() == 2U);
|
||||
CHECK(releaseCount == 0U);
|
||||
checkRemoteUri(firstPresentation, "first.txt");
|
||||
lgClipboardFiles_remotePresentationRelease(firstPresentation);
|
||||
CHECK(lgClipboardFiles_testRemoteDatasetCount() == 1U);
|
||||
CHECK(releaseCount == 1U);
|
||||
CHECK(releasedDataset == firstDataset);
|
||||
CHECK(releasedAcquisition == first);
|
||||
|
||||
const uint64_t secondPresentation =
|
||||
lgClipboardFiles_remotePresentationAcquire();
|
||||
CHECK(secondPresentation != 0);
|
||||
checkRemoteUri(secondPresentation, "second.txt");
|
||||
lgClipboardFiles_remotePresentationDelivered(secondPresentation);
|
||||
lgClipboardFiles_remotePresentationRelease(secondPresentation);
|
||||
const uint64_t thirdDataset = KVMFR_CLIPBOARD_TRANSFER_HELPER |
|
||||
UINT64_C(0x73110003);
|
||||
completeRemoteOffer(thirdDataset, "third.txt");
|
||||
CHECK(lgClipboardFiles_testRemoteDatasetCount() == 2U);
|
||||
CHECK(releaseCount == 1U);
|
||||
CHECK(lgClipboardFiles_testBeginRemoteLookup(secondPresentation));
|
||||
lgClipboardFiles_testExpireRemoteDeliveries();
|
||||
CHECK(lgClipboardFiles_testRemoteDatasetCount() == 2U);
|
||||
CHECK(releaseCount == 1U);
|
||||
lgClipboardFiles_testEndRemoteLookup(secondPresentation);
|
||||
CHECK(lgClipboardFiles_testRemoteDatasetCount() == 1U);
|
||||
CHECK(releaseCount == 2U);
|
||||
CHECK(releasedDataset == secondDataset);
|
||||
lgClipboardFiles_remoteClear();
|
||||
CHECK(lgClipboardFiles_testRemoteDatasetCount() == 0U);
|
||||
CHECK(releaseCount == 3U);
|
||||
|
||||
for (uint64_t i = 0; i < 10; ++i)
|
||||
{
|
||||
const uint64_t dataset = KVMFR_CLIPBOARD_TRANSFER_HELPER |
|
||||
(UINT64_C(0x73120000) + i);
|
||||
const uint64_t acquisition =
|
||||
completeRemoteOffer(dataset, "grace.txt");
|
||||
const uint64_t presentation =
|
||||
lgClipboardFiles_remotePresentationAcquire();
|
||||
CHECK(presentation == acquisition);
|
||||
checkRemoteUri(presentation, "grace.txt");
|
||||
lgClipboardFiles_remotePresentationDelivered(presentation);
|
||||
lgClipboardFiles_remotePresentationRelease(presentation);
|
||||
CHECK(lgClipboardFiles_testRemoteDatasetCount() <= 5U);
|
||||
}
|
||||
lgClipboardFiles_remoteClear();
|
||||
lgClipboardFiles_testExpireRemoteDeliveries();
|
||||
CHECK(lgClipboardFiles_testRemoteDatasetCount() == 0U);
|
||||
|
||||
for (uint64_t i = 0; i < 16; ++i)
|
||||
{
|
||||
const uint64_t oldDataset = KVMFR_CLIPBOARD_TRANSFER_HELPER |
|
||||
(UINT64_C(0x73130000) + i * 2U);
|
||||
const uint64_t oldAcquisition =
|
||||
completeRemoteOffer(oldDataset, "old.txt");
|
||||
const uint64_t presentation =
|
||||
lgClipboardFiles_remotePresentationAcquire();
|
||||
CHECK(presentation == oldAcquisition);
|
||||
const uint64_t currentDataset = oldDataset + 1U;
|
||||
completeRemoteOffer(currentDataset, "current.txt");
|
||||
const unsigned previousReleases = releaseCount;
|
||||
releasePresentationConcurrently(presentation);
|
||||
CHECK(releaseCount == previousReleases + 1U);
|
||||
CHECK(releasedDataset == oldDataset);
|
||||
CHECK(lgClipboardFiles_testRemoteDatasetCount() == 1U);
|
||||
lgClipboardFiles_remoteClear();
|
||||
CHECK(lgClipboardFiles_testRemoteDatasetCount() == 0U);
|
||||
}
|
||||
|
||||
const uint64_t pendingDataset = KVMFR_CLIPBOARD_TRANSFER_HELPER |
|
||||
UINT64_C(0x73140001);
|
||||
CHECK(lgClipboardFiles_remoteOffer(pendingDataset));
|
||||
CHECK(acquiredDataset == pendingDataset);
|
||||
const uint64_t pendingAcquisition = acquiredRequest;
|
||||
const uint64_t replacementDataset = pendingDataset + 1U;
|
||||
const unsigned previousCancels = cancelCount;
|
||||
CHECK(lgClipboardFiles_remoteOffer(replacementDataset));
|
||||
CHECK(cancelCount == previousCancels + 1U);
|
||||
CHECK(cancelledDataset == pendingDataset);
|
||||
CHECK(cancelledRequest == pendingAcquisition);
|
||||
CHECK(cancelledReason == LG_CLIPBOARD_FILE_ERROR_CANCELLED);
|
||||
CHECK(lgClipboardFiles_testRemoteDatasetCount() == 1U);
|
||||
lgClipboardFiles_remoteClear();
|
||||
CHECK(lgClipboardFiles_testRemoteDatasetCount() == 0U);
|
||||
lgClipboardFiles_free();
|
||||
}
|
||||
|
||||
static int testMount(void)
|
||||
{
|
||||
if (access("/dev/fuse", R_OK | W_OK) < 0)
|
||||
return 77;
|
||||
if (!getenv("XDG_RUNTIME_DIR"))
|
||||
CHECK(setenv("XDG_RUNTIME_DIR", "/tmp", 1) == 0);
|
||||
CHECK(lgClipboardFiles_init());
|
||||
lgClipboardFiles_free();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
debug_init();
|
||||
|
||||
if (argc == 2 && !strcmp(argv[1], "mount"))
|
||||
return testMount();
|
||||
if (argc == 2 && !strcmp(argv[1], "failures"))
|
||||
{
|
||||
testRemoteFailures();
|
||||
return 0;
|
||||
}
|
||||
testLocalLifecycle();
|
||||
testLocalUriValidation();
|
||||
testLocalSnapshot();
|
||||
testLocalUnsupportedEntry();
|
||||
testRemoteLifecycle();
|
||||
free(responseData);
|
||||
return 0;
|
||||
}
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -61,6 +62,8 @@ struct Provider
|
||||
unsigned int cancel;
|
||||
unsigned int ready;
|
||||
unsigned int request;
|
||||
unsigned int fileOffer;
|
||||
uint64_t fileDataset[MAX_CALL];
|
||||
LG_ClipboardData noticeTypes[LG_CLIPBOARD_DATA_NONE];
|
||||
size_t noticeCount;
|
||||
LG_ClipboardRequest reqId[MAX_CALL];
|
||||
@@ -86,21 +89,24 @@ struct Provider
|
||||
|
||||
struct Display
|
||||
{
|
||||
unsigned int notice;
|
||||
unsigned int release;
|
||||
unsigned int request;
|
||||
unsigned int ready;
|
||||
unsigned int cancel;
|
||||
LG_ClipboardData noticeType[MAX_CALL];
|
||||
LG_ClipboardRequest reqId[MAX_CALL];
|
||||
LG_ClipboardData reqType[MAX_CALL];
|
||||
bool autoData;
|
||||
LG_ClipboardData autoType;
|
||||
const void * autoBuf;
|
||||
size_t autoSize;
|
||||
bool autoBegin;
|
||||
LG_ClipboardResult readyResult;
|
||||
unsigned int notice;
|
||||
unsigned int release;
|
||||
unsigned int request;
|
||||
unsigned int ready;
|
||||
unsigned int cancel;
|
||||
LG_ClipboardData noticeType[MAX_CALL];
|
||||
LG_ClipboardRequest reqId[MAX_CALL];
|
||||
LG_ClipboardData reqType[MAX_CALL];
|
||||
bool autoData;
|
||||
LG_ClipboardData autoType;
|
||||
const void * autoBuf;
|
||||
size_t autoSize;
|
||||
bool autoBegin;
|
||||
LG_ClipboardResult readyResult;
|
||||
LG_ClipboardCancelReason cancelReason;
|
||||
atomic_bool blockCancel;
|
||||
atomic_bool cancelEntered;
|
||||
atomic_bool releaseCancel;
|
||||
};
|
||||
|
||||
struct Reply
|
||||
@@ -138,6 +144,12 @@ static struct Provider q;
|
||||
static struct Provider r;
|
||||
static struct Display d;
|
||||
|
||||
extern unsigned lgClipboardFilesStubRemoteOfferCount;
|
||||
extern unsigned lgClipboardFilesStubRemoteClearCount;
|
||||
extern uint64_t lgClipboardFilesStubRemoteDataset;
|
||||
extern bool lgClipboardFilesStubRemoteReady;
|
||||
extern bool lgClipboardFilesStubRemoteOfferResult;
|
||||
|
||||
struct AppState g_state;
|
||||
struct AppParams g_params;
|
||||
|
||||
@@ -258,6 +270,15 @@ static bool notify(void * opaque, const LG_ClipboardData types[],
|
||||
return provider->callOK;
|
||||
}
|
||||
|
||||
static bool offerFiles(void * opaque, uint64_t dataset)
|
||||
{
|
||||
struct Provider * provider = opaque;
|
||||
CHECK(dataset);
|
||||
CHECK(provider->fileOffer < MAX_CALL);
|
||||
provider->fileDataset[provider->fileOffer++] = dataset;
|
||||
return provider->callOK;
|
||||
}
|
||||
|
||||
static bool data(void * opaque, LG_ClipboardRequest id,
|
||||
LG_ClipboardData type, const void * buf, size_t size)
|
||||
{
|
||||
@@ -406,6 +427,18 @@ static const LG_ClipboardOps streamOps =
|
||||
.request = request,
|
||||
};
|
||||
|
||||
static const LG_ClipboardOps fileOps =
|
||||
{
|
||||
.name = "files",
|
||||
.attach = attach,
|
||||
.detach = detach,
|
||||
.release = release,
|
||||
.notifyTypes = notify,
|
||||
.offerFiles = offerFiles,
|
||||
.data = data,
|
||||
.request = request,
|
||||
};
|
||||
|
||||
static void dsNotice(LG_ClipboardData type)
|
||||
{
|
||||
CHECK(d.notice < MAX_CALL);
|
||||
@@ -439,6 +472,13 @@ static void dsRequestCancel(LG_ClipboardRequest id,
|
||||
LG_ClipboardCancelReason reason)
|
||||
{
|
||||
(void)id;
|
||||
if (atomic_load_explicit(&d.blockCancel, memory_order_acquire))
|
||||
{
|
||||
atomic_store_explicit(
|
||||
&d.cancelEntered, true, memory_order_release);
|
||||
while (!atomic_load_explicit(&d.releaseCancel, memory_order_acquire))
|
||||
usleep(1000);
|
||||
}
|
||||
++d.cancel;
|
||||
d.cancelReason = reason;
|
||||
}
|
||||
@@ -469,10 +509,18 @@ static void init(void)
|
||||
initProvider(&p);
|
||||
initProvider(&q);
|
||||
initProvider(&r);
|
||||
atomic_init(&d.blockCancel, false);
|
||||
atomic_init(&d.cancelEntered, false);
|
||||
atomic_init(&d.releaseCancel, false);
|
||||
lgClipboardFilesStubRemoteOfferCount = 0;
|
||||
lgClipboardFilesStubRemoteClearCount = 0;
|
||||
lgClipboardFilesStubRemoteDataset = 0;
|
||||
lgClipboardFilesStubRemoteReady = false;
|
||||
lgClipboardFilesStubRemoteOfferResult = true;
|
||||
g_state.ds = &dsOps;
|
||||
g_params.clipboardToVM = true;
|
||||
g_params.clipboardToLocal = true;
|
||||
lgClipboard_init();
|
||||
CHECK(lgClipboard_init());
|
||||
lgClipboard_setLocalAvailable(true);
|
||||
}
|
||||
|
||||
@@ -589,6 +637,214 @@ static void testPreference(void)
|
||||
lgClipboard_free();
|
||||
}
|
||||
|
||||
static void testFilePublication(void)
|
||||
{
|
||||
init();
|
||||
lgClipboard_setFallback(&fileOps, &p);
|
||||
|
||||
const uint64_t dataset = UINT64_C(0x123456789abcdef);
|
||||
lgClipboard_notifyFiles(dataset);
|
||||
CHECK(p.fileOffer == 1);
|
||||
CHECK(p.fileDataset[0] == dataset);
|
||||
|
||||
const LG_ClipboardData files[] = { LG_CLIPBOARD_DATA_FILES };
|
||||
lgClipboard_notifyTypes(files, 1);
|
||||
CHECK(p.notice == 0);
|
||||
CHECK(p.fileOffer == 1);
|
||||
|
||||
lgClipboard_setTransport(&plainOps, &r);
|
||||
CHECK(r.attach == 1);
|
||||
CHECK(r.release == 1);
|
||||
const uint64_t replacement = dataset + 1U;
|
||||
lgClipboard_notifyFiles(replacement);
|
||||
CHECK(r.release == 2);
|
||||
lgClipboard_dropTransport();
|
||||
CHECK(p.attach == 2);
|
||||
CHECK(p.fileOffer == 2);
|
||||
CHECK(p.fileDataset[1] == replacement);
|
||||
|
||||
lgClipboard_setTransport(&fileOps, &q);
|
||||
CHECK(q.attach == 1);
|
||||
CHECK(q.fileOffer == 1);
|
||||
CHECK(q.fileDataset[0] == replacement);
|
||||
|
||||
lgClipboard_dropTransport();
|
||||
CHECK(p.attach == 3);
|
||||
CHECK(p.fileOffer == 3);
|
||||
CHECK(p.fileDataset[2] == replacement);
|
||||
|
||||
lgClipboard_free();
|
||||
}
|
||||
|
||||
static void testFileReplacement(void)
|
||||
{
|
||||
init();
|
||||
bind(&p);
|
||||
lgClipboardFilesStubRemoteReady = true;
|
||||
|
||||
const uint64_t first = UINT64_C(0x2000000000000001);
|
||||
p.ev->fileOffer(p.evCtx, first);
|
||||
CHECK(lgClipboardFilesStubRemoteOfferCount == 1);
|
||||
CHECK(lgClipboardFilesStubRemoteDataset == first);
|
||||
const LG_ClipboardData files[] = { LG_CLIPBOARD_DATA_FILES };
|
||||
p.ev->notice(p.evCtx, files, 1);
|
||||
CHECK(d.notice == 1);
|
||||
CHECK(d.noticeType[0] == LG_CLIPBOARD_DATA_FILES);
|
||||
|
||||
const LG_ClipboardData text[] = { LG_CLIPBOARD_DATA_TEXT };
|
||||
p.ev->notice(p.evCtx, text, 1);
|
||||
CHECK(lgClipboardFilesStubRemoteClearCount == 1);
|
||||
CHECK(lgClipboardFilesStubRemoteDataset == 0);
|
||||
CHECK(d.notice == 2);
|
||||
CHECK(d.noticeType[1] == LG_CLIPBOARD_DATA_TEXT);
|
||||
|
||||
const uint64_t mixedDataset = first + 1U;
|
||||
p.ev->fileOffer(p.evCtx, mixedDataset);
|
||||
const LG_ClipboardData mixed[] =
|
||||
{
|
||||
LG_CLIPBOARD_DATA_TEXT,
|
||||
LG_CLIPBOARD_DATA_FILES,
|
||||
};
|
||||
p.ev->notice(p.evCtx, mixed, 2);
|
||||
CHECK(lgClipboardFilesStubRemoteClearCount == 1);
|
||||
CHECK(lgClipboardFilesStubRemoteDataset == mixedDataset);
|
||||
CHECK(d.notice == 3);
|
||||
CHECK(d.noticeType[2] == LG_CLIPBOARD_DATA_FILES);
|
||||
|
||||
p.ev->notice(p.evCtx, text, 1);
|
||||
CHECK(lgClipboardFilesStubRemoteClearCount == 2);
|
||||
CHECK(lgClipboardFilesStubRemoteDataset == 0);
|
||||
CHECK(d.notice == 4);
|
||||
CHECK(d.noticeType[3] == LG_CLIPBOARD_DATA_TEXT);
|
||||
lgClipboard_free();
|
||||
}
|
||||
|
||||
static void testFileFailure(void)
|
||||
{
|
||||
init();
|
||||
bind(&p);
|
||||
|
||||
const LG_ClipboardData text[] = { LG_CLIPBOARD_DATA_TEXT };
|
||||
p.ev->notice(p.evCtx, text, 1);
|
||||
CHECK(d.notice == 1);
|
||||
CHECK(d.release == 0);
|
||||
|
||||
const LG_ClipboardData files[] = { LG_CLIPBOARD_DATA_FILES };
|
||||
const uint64_t first = UINT64_C(0x2100000000000001);
|
||||
p.ev->fileOffer(p.evCtx, first);
|
||||
p.ev->notice(p.evCtx, files, 1);
|
||||
CHECK(d.notice == 1);
|
||||
CHECK(d.release == 0);
|
||||
|
||||
const uint64_t second = first + 1U;
|
||||
p.ev->fileOffer(p.evCtx, second);
|
||||
p.ev->notice(p.evCtx, files, 1);
|
||||
CHECK(d.notice == 1);
|
||||
CHECK(d.release == 0);
|
||||
|
||||
lgClipboard_fileRemoteFailed(first);
|
||||
CHECK(d.release == 0);
|
||||
CHECK(lgClipboardFilesStubRemoteDataset == second);
|
||||
|
||||
lgClipboard_fileRemoteFailed(second);
|
||||
CHECK(d.release == 1);
|
||||
lgClipboard_fileRemoteFailed(second);
|
||||
CHECK(d.release == 1);
|
||||
|
||||
p.ev->notice(p.evCtx, text, 1);
|
||||
CHECK(d.notice == 2);
|
||||
lgClipboardFilesStubRemoteOfferResult = false;
|
||||
const unsigned previousClears = lgClipboardFilesStubRemoteClearCount;
|
||||
p.ev->fileOffer(p.evCtx, second + 1U);
|
||||
CHECK(lgClipboardFilesStubRemoteClearCount == previousClears + 1U);
|
||||
p.ev->notice(p.evCtx, files, 1);
|
||||
CHECK(d.notice == 2);
|
||||
CHECK(d.release == 2);
|
||||
|
||||
p.ev->notice(p.evCtx, text, 1);
|
||||
CHECK(d.notice == 3);
|
||||
lgClipboardFilesStubRemoteOfferResult = true;
|
||||
const uint64_t pending = second + 2U;
|
||||
p.ev->fileOffer(p.evCtx, pending);
|
||||
lgClipboard_fileRemoteFailed(pending);
|
||||
CHECK(d.release == 2);
|
||||
p.ev->notice(p.evCtx, files, 1);
|
||||
CHECK(d.notice == 3);
|
||||
CHECK(d.release == 3);
|
||||
|
||||
lgClipboard_free();
|
||||
}
|
||||
|
||||
struct CallbackSerializationTask
|
||||
{
|
||||
atomic_bool switched;
|
||||
atomic_bool availabilityChanged;
|
||||
};
|
||||
|
||||
static void * switchClipboardProvider(void * opaque)
|
||||
{
|
||||
struct CallbackSerializationTask * task = opaque;
|
||||
lgClipboard_setTransport(&plainOps, &q);
|
||||
atomic_store_explicit(&task->switched, true, memory_order_release);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void * clearClipboardAvailability(void * opaque)
|
||||
{
|
||||
struct CallbackSerializationTask * task = opaque;
|
||||
lgClipboard_setLocalAvailable(false);
|
||||
atomic_store_explicit(
|
||||
&task->availabilityChanged, true, memory_order_release);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void waitAtomicBool(const atomic_bool * value)
|
||||
{
|
||||
for (unsigned i = 0; i < 1000U; ++i)
|
||||
{
|
||||
if (atomic_load_explicit(value, memory_order_acquire))
|
||||
return;
|
||||
usleep(1000);
|
||||
}
|
||||
CHECK(false);
|
||||
}
|
||||
|
||||
static void testCallbackSerialization(void)
|
||||
{
|
||||
init();
|
||||
bind(&p);
|
||||
const LG_ClipboardData text[] = { LG_CLIPBOARD_DATA_TEXT };
|
||||
lgClipboard_notifyTypes(text, 1);
|
||||
CHECK(p.ev->request(p.evCtx, 1, LG_CLIPBOARD_DATA_TEXT));
|
||||
CHECK(d.request == 1);
|
||||
|
||||
atomic_store_explicit(&d.blockCancel, true, memory_order_release);
|
||||
struct CallbackSerializationTask task;
|
||||
atomic_init(&task.switched, false);
|
||||
atomic_init(&task.availabilityChanged, false);
|
||||
pthread_t switchThread;
|
||||
CHECK(pthread_create(&switchThread, NULL,
|
||||
switchClipboardProvider, &task) == 0);
|
||||
waitAtomicBool(&d.cancelEntered);
|
||||
|
||||
pthread_t availabilityThread;
|
||||
CHECK(pthread_create(&availabilityThread, NULL,
|
||||
clearClipboardAvailability, &task) == 0);
|
||||
usleep(20000);
|
||||
CHECK(!atomic_load_explicit(
|
||||
&task.availabilityChanged, memory_order_acquire));
|
||||
|
||||
atomic_store_explicit(&d.releaseCancel, true, memory_order_release);
|
||||
CHECK(pthread_join(switchThread, NULL) == 0);
|
||||
CHECK(pthread_join(availabilityThread, NULL) == 0);
|
||||
CHECK(atomic_load_explicit(&task.switched, memory_order_acquire));
|
||||
CHECK(atomic_load_explicit(
|
||||
&task.availabilityChanged, memory_order_acquire));
|
||||
CHECK(d.cancel == 1);
|
||||
CHECK(d.cancelReason == LG_CLIPBOARD_CANCEL_UNAVAILABLE);
|
||||
lgClipboard_free();
|
||||
}
|
||||
|
||||
static void testRequest(void)
|
||||
{
|
||||
init();
|
||||
@@ -1129,6 +1385,10 @@ struct Test
|
||||
static const struct Test tests[] =
|
||||
{
|
||||
{ "preference", testPreference },
|
||||
{ "file-publication", testFilePublication },
|
||||
{ "file-replacement", testFileReplacement },
|
||||
{ "file-failure", testFileFailure },
|
||||
{ "callback-serial", testCallbackSerialization },
|
||||
{ "request" , testRequest },
|
||||
{ "remote-local", testRemoteKeepsLocalRequest },
|
||||
{ "pending-remote", testPendingRemoteKeepsLocalRequest },
|
||||
|
||||
@@ -143,7 +143,7 @@ static SpiceClipboard * setup(void)
|
||||
g_state.ds = &displayOps;
|
||||
g_params.clipboardToVM = true;
|
||||
g_params.clipboardToLocal = true;
|
||||
lgClipboard_init();
|
||||
CHECK(lgClipboard_init());
|
||||
lgClipboard_setLocalAvailable(true);
|
||||
lgClipboard_setFallback(spiceClipboard_getOps(), clipboard);
|
||||
return clipboard;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "wayland.h"
|
||||
#include "test.h"
|
||||
#include "../src/clipboard.h"
|
||||
#include "../src/clipboard_files.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
@@ -158,6 +159,18 @@ struct Log
|
||||
bool expectReleaseLocked;
|
||||
bool firing;
|
||||
unsigned int releaseLockedN;
|
||||
unsigned int fileSetN;
|
||||
unsigned int fileClearN;
|
||||
unsigned int presentationAcquireN;
|
||||
unsigned int presentationDeliveredN;
|
||||
unsigned int presentationReleaseN;
|
||||
uint64_t presentationDelivered[MAX_SOURCE];
|
||||
uint64_t presentationReleased[MAX_SOURCE];
|
||||
char fileMime[80];
|
||||
uint8_t fileData[MAX_TRANSFER];
|
||||
size_t fileSize;
|
||||
bool fileSetOK;
|
||||
bool presentationOK;
|
||||
};
|
||||
|
||||
struct WaylandDSState wlWm;
|
||||
@@ -556,6 +569,67 @@ bool lgClipboard_requestReady(LG_ClipboardRequest request)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool lgClipboardFiles_setLocal(const char * mime,
|
||||
const void * data, size_t size)
|
||||
{
|
||||
CHECK(mime);
|
||||
CHECK(!size || data);
|
||||
CHECK(size <= sizeof(rec.fileData));
|
||||
++rec.fileSetN;
|
||||
snprintf(rec.fileMime, sizeof(rec.fileMime), "%s", mime);
|
||||
rec.fileSize = size;
|
||||
if (size)
|
||||
memcpy(rec.fileData, data, size);
|
||||
return rec.fileSetOK;
|
||||
}
|
||||
|
||||
void lgClipboardFiles_clearLocal(void)
|
||||
{
|
||||
++rec.fileClearN;
|
||||
}
|
||||
|
||||
uint64_t lgClipboardFiles_remotePresentationAcquire(void)
|
||||
{
|
||||
++rec.presentationAcquireN;
|
||||
return rec.presentationOK ? 1000U + rec.presentationAcquireN : 0;
|
||||
}
|
||||
|
||||
bool lgClipboardFiles_getRemotePresentation(uint64_t presentation,
|
||||
const char * mime, char ** data, size_t * size)
|
||||
{
|
||||
CHECK(presentation >= 1001U);
|
||||
const char * value;
|
||||
if (!strcmp(mime, "text/uri-list"))
|
||||
value = "file:///run/user/1000/looking-glass/guest.txt\r\n";
|
||||
else if (!strcmp(mime, "x-special/gnome-copied-files"))
|
||||
value = "copy\nfile:///run/user/1000/looking-glass/guest.txt\r\n";
|
||||
else if (!strcmp(mime, "application/x-kde-cutselection"))
|
||||
value = "0";
|
||||
else
|
||||
return false;
|
||||
*size = strlen(value);
|
||||
*data = malloc(*size);
|
||||
CHECK(*data);
|
||||
memcpy(*data, value, *size);
|
||||
return true;
|
||||
}
|
||||
|
||||
void lgClipboardFiles_remotePresentationDelivered(uint64_t presentation)
|
||||
{
|
||||
CHECK(presentation);
|
||||
CHECK(rec.presentationDeliveredN <
|
||||
ARRAY_LENGTH(rec.presentationDelivered));
|
||||
rec.presentationDelivered[rec.presentationDeliveredN++] = presentation;
|
||||
}
|
||||
|
||||
void lgClipboardFiles_remotePresentationRelease(uint64_t presentation)
|
||||
{
|
||||
CHECK(presentation);
|
||||
CHECK(rec.presentationReleaseN <
|
||||
ARRAY_LENGTH(rec.presentationReleased));
|
||||
rec.presentationReleased[rec.presentationReleaseN++] = presentation;
|
||||
}
|
||||
|
||||
static const struct wl_data_device_listener * deviceListener(void)
|
||||
{
|
||||
CHECK(proto.device.listener);
|
||||
@@ -620,6 +694,8 @@ static void start(void)
|
||||
rec.beginResult = LG_CLIPBOARD_RESULT_ACCEPTED;
|
||||
rec.chunkResult = LG_CLIPBOARD_RESULT_ACCEPTED;
|
||||
rec.endResult = LG_CLIPBOARD_RESULT_ACCEPTED;
|
||||
rec.fileSetOK = true;
|
||||
rec.presentationOK = true;
|
||||
proto.manager.kind = PROXY_MANAGER;
|
||||
proto.device.kind = PROXY_DEVICE;
|
||||
wlWm.dataDeviceManager =
|
||||
@@ -697,6 +773,121 @@ static void testMime(void)
|
||||
CHECK(proto.dirtyDestroyN == 0);
|
||||
}
|
||||
|
||||
static void testFileImport(void)
|
||||
{
|
||||
start();
|
||||
const char payload[] =
|
||||
"copy\nfile:///home/user/first.txt\r\nfile:///home/user/second.txt\r\n";
|
||||
proto.receiveData = payload;
|
||||
proto.receiveSize = sizeof(payload) - 1U;
|
||||
|
||||
struct Proxy * offer = newOffer();
|
||||
offerMime(offer, "text/plain;charset=utf-8");
|
||||
offerMime(offer, "text/uri-list");
|
||||
offerMime(offer, "x-special/gnome-copied-files");
|
||||
offerMime(offer, "application/x-kde-cutselection");
|
||||
selectOffer(offer);
|
||||
|
||||
CHECK(rec.noticeN == 0);
|
||||
CHECK(proto.receiveN == 1);
|
||||
CHECK(strcmp(proto.receiveMime,
|
||||
"x-special/gnome-copied-files") == 0);
|
||||
CHECK(rec.pollN == 1);
|
||||
pollFire(0, EPOLLIN);
|
||||
CHECK(rec.fileSetN == 1);
|
||||
CHECK(strcmp(rec.fileMime,
|
||||
"x-special/gnome-copied-files") == 0);
|
||||
CHECK(rec.fileSize == sizeof(payload) - 1U);
|
||||
CHECK(memcmp(rec.fileData, payload, sizeof(payload) - 1U) == 0);
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
static void testFileSource(void)
|
||||
{
|
||||
start();
|
||||
waylandCBNotice(LG_CLIPBOARD_DATA_FILES);
|
||||
CHECK(rec.presentationAcquireN == 1);
|
||||
CHECK(rec.presentationReleaseN == 0);
|
||||
CHECK(proto.sourceN == 1);
|
||||
struct Proxy * source = &proto.source[0];
|
||||
CHECK(source->mimeN == 4);
|
||||
CHECK(strcmp(source->mime[0],
|
||||
"x-special/gnome-copied-files") == 0);
|
||||
CHECK(strcmp(source->mime[1], "text/uri-list") == 0);
|
||||
CHECK(strcmp(source->mime[2],
|
||||
"application/x-kde-cutselection") == 0);
|
||||
|
||||
int fds[2];
|
||||
CHECK(pipe(fds) == 0);
|
||||
sourceListener(source)->send(source->data,
|
||||
(struct wl_data_source *)source, "text/uri-list", fds[1]);
|
||||
CHECK(rec.pollN == 1);
|
||||
CHECK(rec.poll[0].events == EPOLLOUT);
|
||||
pollFire(0, EPOLLOUT);
|
||||
CHECK(rec.presentationDeliveredN == 1);
|
||||
CHECK(rec.presentationDelivered[0] == 1001U);
|
||||
const char expected[] =
|
||||
"file:///run/user/1000/looking-glass/guest.txt\r\n";
|
||||
char actual[sizeof(expected)] = { 0 };
|
||||
CHECK(read(fds[0], actual, sizeof(actual)) ==
|
||||
(ssize_t)(sizeof(expected) - 1U));
|
||||
CHECK(memcmp(actual, expected, sizeof(expected) - 1U) == 0);
|
||||
CHECK(read(fds[0], actual, 1) == 0);
|
||||
CHECK(close(fds[0]) == 0);
|
||||
|
||||
int kde[2];
|
||||
CHECK(pipe(kde) == 0);
|
||||
sourceListener(source)->send(source->data,
|
||||
(struct wl_data_source *)source,
|
||||
"application/x-kde-cutselection", kde[1]);
|
||||
CHECK(rec.pollN == 2);
|
||||
pollFire(1, EPOLLOUT);
|
||||
CHECK(rec.presentationDeliveredN == 1);
|
||||
CHECK(read(kde[0], actual, sizeof(actual)) == 1);
|
||||
CHECK(actual[0] == '0');
|
||||
CHECK(read(kde[0], actual, 1) == 0);
|
||||
CHECK(close(kde[0]) == 0);
|
||||
|
||||
sourceListener(source)->cancelled(source->data,
|
||||
(struct wl_data_source *)source);
|
||||
CHECK(rec.presentationReleaseN == 1);
|
||||
CHECK(rec.presentationReleased[0] == 1001U);
|
||||
finish();
|
||||
CHECK(rec.presentationReleaseN == 1);
|
||||
}
|
||||
|
||||
static void testFileSourceReplacement(void)
|
||||
{
|
||||
start();
|
||||
waylandCBNotice(LG_CLIPBOARD_DATA_FILES);
|
||||
struct Proxy * first = &proto.source[0];
|
||||
CHECK(rec.presentationReleaseN == 0);
|
||||
|
||||
int fds[2];
|
||||
CHECK(pipe(fds) == 0);
|
||||
sourceListener(first)->send(first->data,
|
||||
(struct wl_data_source *)first, "text/uri-list", fds[1]);
|
||||
|
||||
waylandCBNotice(LG_CLIPBOARD_DATA_TEXT);
|
||||
CHECK(rec.presentationReleaseN == 0);
|
||||
CHECK(((struct WCBTransfer *)first->data)->filePresentation == 1001U);
|
||||
|
||||
sourceListener(first)->cancelled(first->data,
|
||||
(struct wl_data_source *)first);
|
||||
CHECK(rec.presentationReleaseN == 0);
|
||||
|
||||
pollFire(0, EPOLLOUT);
|
||||
CHECK(rec.presentationDeliveredN == 1);
|
||||
CHECK(rec.presentationDelivered[0] == 1001U);
|
||||
CHECK(rec.presentationReleaseN == 1);
|
||||
CHECK(rec.presentationReleased[0] == 1001U);
|
||||
char value[64];
|
||||
CHECK(read(fds[0], value, sizeof(value)) > 0);
|
||||
CHECK(close(fds[0]) == 0);
|
||||
finish();
|
||||
}
|
||||
|
||||
static void testSelfCopy(void)
|
||||
{
|
||||
start();
|
||||
@@ -1381,6 +1572,9 @@ struct Test
|
||||
static const struct Test tests[] =
|
||||
{
|
||||
{ "mime" , testMime },
|
||||
{ "file-import", testFileImport },
|
||||
{ "file-source", testFileSource },
|
||||
{ "file-replace", testFileSourceReplacement },
|
||||
{ "replace" , testReplace },
|
||||
{ "read-eof" , testReadEOF },
|
||||
{ "read-block", testReadBlocked },
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "test.h"
|
||||
#include "x11.h"
|
||||
#include "../src/clipboard.h"
|
||||
#include "../src/clipboard_files.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
@@ -56,6 +57,9 @@
|
||||
#define A_BMP 102UL
|
||||
#define A_TIFF 103UL
|
||||
#define A_JPEG 104UL
|
||||
#define A_URI 105UL
|
||||
#define A_GNOME 106UL
|
||||
#define A_KDE 107UL
|
||||
|
||||
struct WindowLog
|
||||
{
|
||||
@@ -136,6 +140,7 @@ struct Log
|
||||
unsigned int grabN;
|
||||
unsigned int ungrabN;
|
||||
unsigned int selectN;
|
||||
unsigned int structureSelectN;
|
||||
LG_ClipboardData requestType;
|
||||
const LG_ClipboardStreamOps * requestStream;
|
||||
void * requestOpaque;
|
||||
@@ -159,6 +164,18 @@ struct Log
|
||||
bool requestCancelSync;
|
||||
bool requestBeginSync;
|
||||
bool fixesOK;
|
||||
bool fileSetOK;
|
||||
bool presentationOK;
|
||||
unsigned int fileSetN;
|
||||
unsigned int fileClearN;
|
||||
unsigned int presentationAcquireN;
|
||||
unsigned int presentationDeliveredN;
|
||||
unsigned int presentationReleaseN;
|
||||
uint64_t presentationDelivered[8];
|
||||
uint64_t presentationReleased[8];
|
||||
char fileMime[80];
|
||||
uint8_t fileData[MAX_TRANSFER];
|
||||
size_t fileSize;
|
||||
};
|
||||
|
||||
struct X11DSState x11;
|
||||
@@ -201,6 +218,12 @@ Atom XInternAtom(Display * display, const char * name, Bool onlyIfExists)
|
||||
return A_TIFF;
|
||||
if (!strcmp(name, "image/jpeg"))
|
||||
return A_JPEG;
|
||||
if (!strcmp(name, "text/uri-list"))
|
||||
return A_URI;
|
||||
if (!strcmp(name, "x-special/gnome-copied-files"))
|
||||
return A_GNOME;
|
||||
if (!strcmp(name, "application/x-kde-cutselection"))
|
||||
return A_KDE;
|
||||
CHECK(false);
|
||||
return None;
|
||||
}
|
||||
@@ -277,7 +300,10 @@ int XConvertSelection(Display * display, Atom selection, Atom target,
|
||||
int XSelectInput(Display * display, Window window, long mask)
|
||||
{
|
||||
CHECK(display == x11.display);
|
||||
CHECK(mask == PropertyChangeMask);
|
||||
CHECK(mask == PropertyChangeMask ||
|
||||
mask == (PropertyChangeMask | StructureNotifyMask));
|
||||
if (mask & StructureNotifyMask)
|
||||
++rec.structureSelectN;
|
||||
CHECK(window != None);
|
||||
return Success;
|
||||
}
|
||||
@@ -549,6 +575,67 @@ bool lgClipboard_requestReady(LG_ClipboardRequest request)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool lgClipboardFiles_setLocal(const char * mime,
|
||||
const void * data, size_t size)
|
||||
{
|
||||
CHECK(mime);
|
||||
CHECK(!size || data);
|
||||
CHECK(size <= sizeof(rec.fileData));
|
||||
++rec.fileSetN;
|
||||
snprintf(rec.fileMime, sizeof(rec.fileMime), "%s", mime);
|
||||
rec.fileSize = size;
|
||||
if (size)
|
||||
memcpy(rec.fileData, data, size);
|
||||
return rec.fileSetOK;
|
||||
}
|
||||
|
||||
void lgClipboardFiles_clearLocal(void)
|
||||
{
|
||||
++rec.fileClearN;
|
||||
}
|
||||
|
||||
uint64_t lgClipboardFiles_remotePresentationAcquire(void)
|
||||
{
|
||||
++rec.presentationAcquireN;
|
||||
return rec.presentationOK ? 2001U : 0;
|
||||
}
|
||||
|
||||
bool lgClipboardFiles_getRemotePresentation(uint64_t presentation,
|
||||
const char * mime, char ** data, size_t * size)
|
||||
{
|
||||
CHECK(presentation >= 2001U);
|
||||
const char * value;
|
||||
if (!strcmp(mime, "text/uri-list"))
|
||||
value = "file:///run/user/1000/looking-glass/guest.txt\r\n";
|
||||
else if (!strcmp(mime, "x-special/gnome-copied-files"))
|
||||
value = "copy\nfile:///run/user/1000/looking-glass/guest.txt\r\n";
|
||||
else if (!strcmp(mime, "application/x-kde-cutselection"))
|
||||
value = "0";
|
||||
else
|
||||
return false;
|
||||
*size = strlen(value);
|
||||
*data = malloc(*size);
|
||||
CHECK(*data);
|
||||
memcpy(*data, value, *size);
|
||||
return true;
|
||||
}
|
||||
|
||||
void lgClipboardFiles_remotePresentationDelivered(uint64_t presentation)
|
||||
{
|
||||
CHECK(presentation);
|
||||
CHECK(rec.presentationDeliveredN <
|
||||
ARRAY_LENGTH(rec.presentationDelivered));
|
||||
rec.presentationDelivered[rec.presentationDeliveredN++] = presentation;
|
||||
}
|
||||
|
||||
void lgClipboardFiles_remotePresentationRelease(uint64_t presentation)
|
||||
{
|
||||
CHECK(presentation);
|
||||
CHECK(rec.presentationReleaseN <
|
||||
ARRAY_LENGTH(rec.presentationReleased));
|
||||
rec.presentationReleased[rec.presentationReleaseN++] = presentation;
|
||||
}
|
||||
|
||||
static struct Property * prop(void)
|
||||
{
|
||||
CHECK(rec.propN < ARRAY_LENGTH(rec.prop));
|
||||
@@ -633,6 +720,17 @@ static void owner(Window owner)
|
||||
CHECK(x11CBEventThread((const XEvent *)&event));
|
||||
}
|
||||
|
||||
static void selectionClear(Window owner)
|
||||
{
|
||||
rec.owner = owner;
|
||||
XEvent event = {};
|
||||
event.xselectionclear.type = SelectionClear;
|
||||
event.xselectionclear.display = x11.display;
|
||||
event.xselectionclear.window = x11.window;
|
||||
event.xselectionclear.selection = x11atoms.CLIPBOARD;
|
||||
CHECK(x11CBEventThread(&event));
|
||||
}
|
||||
|
||||
static void discover(Window ownerWindow,
|
||||
const unsigned long * targets, size_t count)
|
||||
{
|
||||
@@ -676,12 +774,14 @@ static void start(void)
|
||||
memset(&x11, 0, sizeof(x11));
|
||||
memset(&x11atoms, 0, sizeof(x11atoms));
|
||||
memset(&rec, 0, sizeof(rec));
|
||||
rec.nextWindow = 1000;
|
||||
rec.requestOK = true;
|
||||
rec.fixesOK = true;
|
||||
rec.beginResult = LG_CLIPBOARD_RESULT_ACCEPTED;
|
||||
rec.chunkResult = LG_CLIPBOARD_RESULT_ACCEPTED;
|
||||
rec.endResult = LG_CLIPBOARD_RESULT_ACCEPTED;
|
||||
rec.nextWindow = 1000;
|
||||
rec.requestOK = true;
|
||||
rec.fixesOK = true;
|
||||
rec.beginResult = LG_CLIPBOARD_RESULT_ACCEPTED;
|
||||
rec.chunkResult = LG_CLIPBOARD_RESULT_ACCEPTED;
|
||||
rec.endResult = LG_CLIPBOARD_RESULT_ACCEPTED;
|
||||
rec.fileSetOK = true;
|
||||
rec.presentationOK = true;
|
||||
|
||||
pthread_mutex_lock(&readRaceLock);
|
||||
readRacePause = false;
|
||||
@@ -727,6 +827,189 @@ static void testTargets(void)
|
||||
finish();
|
||||
}
|
||||
|
||||
static void testFileImport(void)
|
||||
{
|
||||
start();
|
||||
const unsigned long targets[] = { A_TEXT, A_URI };
|
||||
discover(601, targets, ARRAY_LENGTH(targets));
|
||||
CHECK(rec.convertN == 2);
|
||||
const struct ConvertLog * convert = &rec.convert[1];
|
||||
CHECK(convert->target == A_URI);
|
||||
CHECK(convert->property == x11atoms.SEL_DATA);
|
||||
CHECK(rec.noticeN == 0);
|
||||
|
||||
const char payload[] =
|
||||
"file:///home/user/first.txt\r\nfile:///home/user/second.txt\r\n";
|
||||
prop8(A_URI, payload, sizeof(payload) - 1U);
|
||||
selection(convert->requestor, A_URI, x11atoms.SEL_DATA);
|
||||
CHECK(rec.fileSetN == 1);
|
||||
CHECK(strcmp(rec.fileMime, "text/uri-list") == 0);
|
||||
CHECK(rec.fileSize == sizeof(payload) - 1U);
|
||||
CHECK(memcmp(rec.fileData, payload, sizeof(payload) - 1U) == 0);
|
||||
CHECK(rec.noticeN == 0);
|
||||
finish();
|
||||
}
|
||||
|
||||
static void testFileImportIncr(void)
|
||||
{
|
||||
start();
|
||||
const unsigned long targets[] = { A_URI, A_TEXT, A_GNOME };
|
||||
discover(602, targets, ARRAY_LENGTH(targets));
|
||||
CHECK(rec.convertN == 2);
|
||||
const struct ConvertLog * convert = &rec.convert[1];
|
||||
CHECK(convert->target == A_GNOME);
|
||||
|
||||
const unsigned long capacity = 128;
|
||||
prop32(x11atoms.INCR, &capacity, 1);
|
||||
selection(convert->requestor, A_GNOME, x11atoms.SEL_DATA);
|
||||
CHECK(rec.fileSetN == 0);
|
||||
|
||||
const char first[] = "copy\nfile:///home/user/";
|
||||
const char second[] = "large%20file.txt\r\n";
|
||||
prop8(A_GNOME, first, sizeof(first) - 1U);
|
||||
property(convert->requestor);
|
||||
CHECK(rec.fileSetN == 0);
|
||||
prop8(A_GNOME, second, sizeof(second) - 1U);
|
||||
property(convert->requestor);
|
||||
propNull(A_GNOME, 8, 0);
|
||||
property(convert->requestor);
|
||||
|
||||
CHECK(rec.fileSetN == 1);
|
||||
CHECK(strcmp(rec.fileMime,
|
||||
"x-special/gnome-copied-files") == 0);
|
||||
CHECK(rec.fileSize == sizeof(first) + sizeof(second) - 2U);
|
||||
CHECK(memcmp(rec.fileData,
|
||||
"copy\nfile:///home/user/large%20file.txt\r\n",
|
||||
rec.fileSize) == 0);
|
||||
CHECK(rec.noticeN == 0);
|
||||
finish();
|
||||
}
|
||||
|
||||
static void testFileSource(void)
|
||||
{
|
||||
start();
|
||||
x11CBNotice(LG_CLIPBOARD_DATA_FILES);
|
||||
CHECK(rec.owner == x11.window);
|
||||
CHECK(rec.presentationAcquireN == 1);
|
||||
CHECK(rec.presentationReleaseN == 0);
|
||||
|
||||
selectionRequest(x11atoms.TARGETS, 710);
|
||||
CHECK(rec.changeN == 1);
|
||||
CHECK(rec.change[0].format == 32);
|
||||
CHECK(rec.change[0].count == 4);
|
||||
const Atom * targets = (const Atom *)rec.change[0].data;
|
||||
CHECK(targets[0] == x11atoms.TARGETS);
|
||||
CHECK(targets[1] == A_URI);
|
||||
CHECK(targets[2] == A_GNOME);
|
||||
CHECK(targets[3] == A_KDE);
|
||||
|
||||
selectionRequest(A_URI, 711);
|
||||
CHECK(rec.presentationAcquireN == 2);
|
||||
CHECK(rec.structureSelectN == 1);
|
||||
CHECK(rec.changeN == 2);
|
||||
CHECK(rec.change[1].type == x11atoms.INCR);
|
||||
CHECK(rec.change[1].format == 32);
|
||||
CHECK(rec.change[1].count == 1);
|
||||
CHECK(rec.sendN == 2);
|
||||
|
||||
propertyState(900, 711, PropertyDelete);
|
||||
CHECK(rec.changeN == 3);
|
||||
CHECK(rec.change[2].type == A_URI);
|
||||
CHECK(rec.change[2].format == 8);
|
||||
const char expected[] =
|
||||
"file:///run/user/1000/looking-glass/guest.txt\r\n";
|
||||
CHECK(rec.change[2].size == sizeof(expected) - 1U);
|
||||
CHECK(memcmp(rec.change[2].data,
|
||||
expected, sizeof(expected) - 1U) == 0);
|
||||
|
||||
propertyState(900, 711, PropertyDelete);
|
||||
CHECK(rec.changeN == 4);
|
||||
CHECK(rec.change[3].type == A_URI);
|
||||
CHECK(rec.change[3].count == 0);
|
||||
CHECK(rec.presentationDeliveredN == 1);
|
||||
CHECK(rec.presentationDelivered[0] == 2001U);
|
||||
CHECK(rec.presentationReleaseN == 1);
|
||||
CHECK(rec.presentationReleased[0] == 2001U);
|
||||
|
||||
selectionRequest(A_KDE, 712);
|
||||
CHECK(rec.presentationAcquireN == 3);
|
||||
CHECK(rec.structureSelectN == 2);
|
||||
CHECK(rec.changeN == 5);
|
||||
CHECK(rec.change[4].type == x11atoms.INCR);
|
||||
propertyState(900, 712, PropertyDelete);
|
||||
CHECK(rec.changeN == 6);
|
||||
CHECK(rec.change[5].type == A_KDE);
|
||||
CHECK(rec.change[5].size == 1);
|
||||
CHECK(rec.change[5].data[0] == '0');
|
||||
propertyState(900, 712, PropertyDelete);
|
||||
CHECK(rec.changeN == 7);
|
||||
CHECK(rec.change[6].type == A_KDE);
|
||||
CHECK(rec.change[6].count == 0);
|
||||
CHECK(rec.presentationDeliveredN == 1);
|
||||
CHECK(rec.presentationReleaseN == 2);
|
||||
CHECK(rec.presentationReleased[1] == 2001U);
|
||||
|
||||
x11CBNotice(LG_CLIPBOARD_DATA_TEXT);
|
||||
CHECK(rec.presentationReleaseN == 3);
|
||||
CHECK(rec.presentationReleased[2] == 2001U);
|
||||
finish();
|
||||
CHECK(rec.presentationReleaseN == 3);
|
||||
}
|
||||
|
||||
static void testFileSourceActiveReplacement(void)
|
||||
{
|
||||
start();
|
||||
x11CBNotice(LG_CLIPBOARD_DATA_FILES);
|
||||
selectionRequest(A_URI, 713);
|
||||
CHECK(rec.changeN == 1);
|
||||
CHECK(rec.change[0].type == x11atoms.INCR);
|
||||
CHECK(rec.presentationAcquireN == 2);
|
||||
CHECK(rec.presentationReleaseN == 0);
|
||||
|
||||
x11CBNotice(LG_CLIPBOARD_DATA_TEXT);
|
||||
CHECK(rec.presentationReleaseN == 1);
|
||||
CHECK(rec.changeN == 1);
|
||||
|
||||
selectionClear(901);
|
||||
CHECK(rec.presentationReleaseN == 1);
|
||||
CHECK(rec.changeN == 1);
|
||||
|
||||
propertyState(900, 713, PropertyDelete);
|
||||
CHECK(rec.changeN == 2);
|
||||
CHECK(rec.change[1].type == A_URI);
|
||||
CHECK(rec.change[1].size > 0);
|
||||
CHECK(rec.presentationDeliveredN == 0);
|
||||
|
||||
propertyState(900, 713, PropertyDelete);
|
||||
CHECK(rec.changeN == 3);
|
||||
CHECK(rec.change[2].type == A_URI);
|
||||
CHECK(rec.change[2].count == 0);
|
||||
CHECK(rec.presentationDeliveredN == 1);
|
||||
CHECK(rec.presentationDelivered[0] == 2001U);
|
||||
CHECK(rec.presentationReleaseN == 2);
|
||||
CHECK(rec.presentationReleased[1] == 2001U);
|
||||
finish();
|
||||
CHECK(rec.presentationReleaseN == 2);
|
||||
|
||||
start();
|
||||
x11CBNotice(LG_CLIPBOARD_DATA_FILES);
|
||||
selectionRequest(A_URI, 714);
|
||||
CHECK(rec.changeN == 1);
|
||||
CHECK(rec.presentationAcquireN == 2);
|
||||
|
||||
XEvent destroyed = {};
|
||||
destroyed.xdestroywindow.type = DestroyNotify;
|
||||
destroyed.xdestroywindow.display = x11.display;
|
||||
destroyed.xdestroywindow.window = 900;
|
||||
CHECK(x11CBEventThread(&destroyed));
|
||||
CHECK(rec.changeN == 1);
|
||||
CHECK(rec.presentationReleaseN == 1);
|
||||
CHECK(rec.presentationDeliveredN == 0);
|
||||
|
||||
finish();
|
||||
CHECK(rec.presentationReleaseN == 2);
|
||||
}
|
||||
|
||||
static void testNormal(void)
|
||||
{
|
||||
start();
|
||||
@@ -1146,18 +1429,22 @@ struct Test
|
||||
|
||||
static const struct Test tests[] =
|
||||
{
|
||||
{ "targets" , testTargets },
|
||||
{ "normal" , testNormal },
|
||||
{ "incr" , testIncr },
|
||||
{ "incr-block", testIncrBlocked },
|
||||
{ "incr-ready", testIncrReadyRace },
|
||||
{ "incr-cancel", testIncrCancelRace },
|
||||
{ "incr-large", testIncrLargeProperty },
|
||||
{ "malformed", testMalformed },
|
||||
{ "replace" , testReplace },
|
||||
{ "source" , testSource },
|
||||
{ "source-early", testSourceBeginDuringRequest },
|
||||
{ "teardown" , testTeardown },
|
||||
{ "targets" , testTargets },
|
||||
{ "file-import" , testFileImport },
|
||||
{ "file-incr" , testFileImportIncr },
|
||||
{ "file-source" , testFileSource },
|
||||
{ "file-source-active-replace", testFileSourceActiveReplacement },
|
||||
{ "normal" , testNormal },
|
||||
{ "incr" , testIncr },
|
||||
{ "incr-block" , testIncrBlocked },
|
||||
{ "incr-ready" , testIncrReadyRace },
|
||||
{ "incr-cancel" , testIncrCancelRace },
|
||||
{ "incr-large" , testIncrLargeProperty },
|
||||
{ "malformed" , testMalformed },
|
||||
{ "replace" , testReplace },
|
||||
{ "source" , testSource },
|
||||
{ "source-early" , testSourceBeginDuringRequest },
|
||||
{ "teardown" , testTeardown },
|
||||
};
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
|
||||
@@ -103,6 +103,7 @@ static bool lgType(LG_ClipboardData source, PSDataType * type)
|
||||
case LG_CLIPBOARD_DATA_BMP : *type = SPICE_DATA_BMP ; return true;
|
||||
case LG_CLIPBOARD_DATA_TIFF : *type = SPICE_DATA_TIFF; return true;
|
||||
case LG_CLIPBOARD_DATA_JPEG : *type = SPICE_DATA_JPEG; return true;
|
||||
case LG_CLIPBOARD_DATA_FILES: return false;
|
||||
case LG_CLIPBOARD_DATA_NONE : *type = SPICE_DATA_NONE; return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user