Files
LookingGlass/client/tests/wayland_clipboard_test.c
2026-08-14 19:09:11 +10:00

1624 lines
44 KiB
C

/**
* 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 "wayland.h"
#include "test.h"
#include "../src/clipboard.h"
#include "../src/clipboard_files.h"
#include "common/debug.h"
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <unistd.h>
#define ARRAY_LENGTH(a) (sizeof(a) / sizeof((a)[0]))
#define MAX_ABORT 8U
#define MAX_DATA 4U
#define MAX_MIME 12U
#define MAX_NOTICE 8U
#define MAX_OFFER 8U
#define MAX_POLL 8U
#define MAX_SOURCE 4U
#define MAX_TRANSFER 8192U
enum ProxyKind
{
PROXY_MANAGER,
PROXY_DEVICE,
PROXY_OFFER,
PROXY_SOURCE,
};
struct Proxy
{
enum ProxyKind kind;
void (**listener)(void);
void * data;
void * user;
bool dead;
char mime[MAX_MIME][80];
unsigned int mimeN;
};
struct Proto
{
struct Proxy manager;
struct Proxy device;
struct Proxy offer[MAX_OFFER];
struct Proxy source[MAX_SOURCE];
unsigned int offerN;
unsigned int sourceN;
unsigned int offerDestroyN;
unsigned int sourceDestroyN;
unsigned int deviceReleaseN;
unsigned int dirtyDestroyN;
unsigned int receiveN;
unsigned int setActionsN;
unsigned int selectionSetN;
char receiveMime[80];
const void * receiveData;
size_t receiveSize;
int receiveFd;
bool keepReceiveOpen;
struct Proxy * selection;
uint32_t serial;
};
struct Poll
{
int fd;
WaylandPollCallback callback;
WaylandPollCleanup cleanup;
void * opaque;
uint32_t events;
bool active;
bool retired;
};
struct TypeNotice
{
LG_ClipboardData type[LG_CLIPBOARD_DATA_NONE];
size_t count;
};
struct DataNotice
{
LG_ClipboardRequest request;
LG_ClipboardData type;
uint8_t data[MAX_TRANSFER];
size_t size;
};
struct StreamNotice
{
LG_ClipboardRequest request;
LG_ClipboardData type;
uint64_t sizeHint;
uint64_t finalSize;
uint8_t data[MAX_TRANSFER];
size_t size;
unsigned int beginN;
unsigned int chunkN;
unsigned int endN;
};
struct Log
{
struct Poll poll[MAX_POLL];
struct TypeNotice notice[MAX_NOTICE];
struct DataNotice data[MAX_DATA];
struct StreamNotice streamData;
LG_ClipboardRequest abort[MAX_ABORT];
unsigned int pollN;
unsigned int pollUnregisterN;
unsigned int pollCleanupN;
unsigned int noticeN;
unsigned int dataN;
unsigned int abortN;
unsigned int releaseN;
unsigned int requestN;
unsigned int requestReadyN;
LG_ClipboardData requestType;
const LG_ClipboardStreamOps * requestStream;
void * requestOpaque;
LG_ClipboardRequest requestId;
LG_ClipboardResult beginResult;
LG_ClipboardResult chunkResult;
LG_ClipboardResult endResult;
LG_ClipboardResult requestBeginResult;
bool readyDuringChunk;
bool readyDuringEnd;
bool pollOK;
bool requestOK;
bool requestCancelSync;
bool requestBeginSync;
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;
struct WCBState wlCb;
static struct Proto proto;
static struct Log rec;
const struct wl_interface wl_data_device_interface =
{
.name = "wl_data_device",
.version = 3,
};
const struct wl_interface wl_data_source_interface =
{
.name = "wl_data_source",
.version = 3,
};
uint32_t wl_proxy_get_version(struct wl_proxy * proxy)
{
CHECK(!((struct Proxy *)proxy)->dead);
return 3;
}
void wl_proxy_set_user_data(struct wl_proxy * proxy, void * data)
{
struct Proxy * p = (struct Proxy *)proxy;
CHECK(!p->dead);
p->user = data;
}
void * wl_proxy_get_user_data(struct wl_proxy * proxy)
{
struct Proxy * p = (struct Proxy *)proxy;
CHECK(!p->dead);
return p->user;
}
int wl_proxy_add_listener(struct wl_proxy * proxy,
void (**listener)(void), void * data)
{
struct Proxy * p = (struct Proxy *)proxy;
CHECK(!p->dead);
CHECK(!p->listener);
p->listener = listener;
p->data = data;
return 0;
}
static void destroy(struct Proxy * p)
{
CHECK(p);
CHECK(!p->dead);
p->dead = true;
if (p->user)
++proto.dirtyDestroyN;
switch (p->kind)
{
case PROXY_OFFER:
++proto.offerDestroyN;
break;
case PROXY_SOURCE:
++proto.sourceDestroyN;
break;
case PROXY_DEVICE:
++proto.deviceReleaseN;
break;
default:
CHECK(false);
}
}
static void copyMime(struct Proxy * p, const char * mime)
{
CHECK(p->mimeN < ARRAY_LENGTH(p->mime));
const int n = snprintf(p->mime[p->mimeN], sizeof(p->mime[p->mimeN]),
"%s", mime);
CHECK(n >= 0);
CHECK((size_t)n < sizeof(p->mime[p->mimeN]));
++p->mimeN;
}
static void sendData(int fd, const void * data, size_t size)
{
const uint8_t * pos = data;
while (size)
{
const ssize_t n = write(fd, pos, size);
CHECK(n > 0);
pos += n;
size -= n;
}
}
struct wl_proxy * wl_proxy_marshal_flags(struct wl_proxy * proxy,
uint32_t opcode, const struct wl_interface * interface,
uint32_t version, uint32_t flags, ...)
{
struct Proxy * p = (struct Proxy *)proxy;
CHECK(p);
CHECK(!p->dead);
CHECK(version == 3);
if (flags & WL_MARSHAL_FLAG_DESTROY)
{
CHECK(!interface);
switch (p->kind)
{
case PROXY_DEVICE:
CHECK(opcode == WL_DATA_DEVICE_RELEASE);
break;
case PROXY_OFFER:
CHECK(opcode == WL_DATA_OFFER_DESTROY);
break;
case PROXY_SOURCE:
CHECK(opcode == WL_DATA_SOURCE_DESTROY);
break;
default:
CHECK(false);
}
destroy(p);
return NULL;
}
va_list ap;
va_start(ap, flags);
switch (p->kind)
{
case PROXY_MANAGER:
if (opcode == WL_DATA_DEVICE_MANAGER_GET_DATA_DEVICE)
{
CHECK(interface == &wl_data_device_interface);
p = &proto.device;
}
else
{
CHECK(opcode == WL_DATA_DEVICE_MANAGER_CREATE_DATA_SOURCE);
CHECK(interface == &wl_data_source_interface);
CHECK(proto.sourceN < ARRAY_LENGTH(proto.source));
p = &proto.source[proto.sourceN++];
p->kind = PROXY_SOURCE;
}
break;
case PROXY_DEVICE:
CHECK(opcode == WL_DATA_DEVICE_SET_SELECTION);
CHECK(!interface);
proto.selection = (struct Proxy *)va_arg(ap, struct wl_data_source *);
proto.serial = va_arg(ap, unsigned int);
++proto.selectionSetN;
p = NULL;
break;
case PROXY_OFFER:
CHECK(!interface);
if (opcode == WL_DATA_OFFER_RECEIVE)
{
copyMime(p, va_arg(ap, const char *));
const int fd = va_arg(ap, int);
++proto.receiveN;
snprintf(proto.receiveMime, sizeof(proto.receiveMime),
"%s", p->mime[p->mimeN - 1]);
if (proto.keepReceiveOpen)
{
CHECK(proto.receiveFd < 0);
proto.receiveFd = dup(fd);
CHECK(proto.receiveFd >= 0);
}
if (proto.receiveSize)
sendData(fd, proto.receiveData, proto.receiveSize);
}
else
{
CHECK(opcode == WL_DATA_OFFER_SET_ACTIONS);
CHECK(va_arg(ap, unsigned int) ==
WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE);
CHECK(va_arg(ap, unsigned int) ==
WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE);
++proto.setActionsN;
}
p = NULL;
break;
case PROXY_SOURCE:
CHECK(opcode == WL_DATA_SOURCE_OFFER);
CHECK(!interface);
copyMime(p, va_arg(ap, const char *));
p = NULL;
break;
}
va_end(ap);
return (struct wl_proxy *)p;
}
static void pollCleanup(struct Poll * poll)
{
CHECK(poll->retired);
poll->retired = false;
++rec.pollCleanupN;
if (poll->cleanup)
poll->cleanup(poll->opaque);
poll->cleanup = NULL;
poll->opaque = NULL;
}
bool waylandPollRegisterWithCleanup(int fd, WaylandPollCallback callback,
void * opaque, WaylandPollCleanup cleanup, uint32_t events)
{
if (!rec.pollOK)
return false;
CHECK(rec.pollN < ARRAY_LENGTH(rec.poll));
struct Poll * poll = &rec.poll[rec.pollN++];
poll->fd = fd;
poll->callback = callback;
poll->cleanup = cleanup;
poll->opaque = opaque;
poll->events = events;
poll->active = true;
return true;
}
bool waylandPollUnregister(int fd)
{
for (unsigned int i = 0; i < rec.pollN; ++i)
{
struct Poll * poll = &rec.poll[i];
if (!poll->active || poll->fd != fd)
continue;
poll->active = false;
poll->retired = true;
++rec.pollUnregisterN;
if (!rec.firing)
pollCleanup(poll);
return true;
}
return false;
}
bool waylandPollUpdate(int fd, uint32_t events)
{
for (unsigned int i = 0; i < rec.pollN; ++i)
{
struct Poll * poll = &rec.poll[i];
if (!poll->active || poll->fd != fd)
continue;
poll->events = events;
return true;
}
return false;
}
static void pollFire(unsigned int no, uint32_t events)
{
CHECK(no < rec.pollN);
struct Poll * poll = &rec.poll[no];
CHECK(poll->active);
CHECK(events & (poll->events | EPOLLERR));
rec.firing = true;
poll->callback(events, poll->opaque);
rec.firing = false;
for (unsigned int i = 0; i < rec.pollN; ++i)
if (rec.poll[i].retired)
pollCleanup(&rec.poll[i]);
}
void lgClipboard_notifyTypes(
const LG_ClipboardData types[], size_t count)
{
CHECK(rec.noticeN < ARRAY_LENGTH(rec.notice));
CHECK(count <= ARRAY_LENGTH(rec.notice[0].type));
struct TypeNotice * notice = &rec.notice[rec.noticeN++];
memcpy(notice->type, types, count * sizeof(*types));
notice->count = count;
}
void lgClipboard_release(void)
{
if (rec.expectReleaseLocked)
{
CHECK(atomic_flag_test_and_set_explicit(
&wlCb.lock, memory_order_acquire));
++rec.releaseLockedN;
}
++rec.releaseN;
}
void lgClipboard_abort(LG_ClipboardRequest request)
{
CHECK(rec.abortN < ARRAY_LENGTH(rec.abort));
rec.abort[rec.abortN++] = request;
}
void lgClipboard_data(LG_ClipboardRequest request,
LG_ClipboardData type, const void * data, size_t size)
{
CHECK(rec.dataN < ARRAY_LENGTH(rec.data));
CHECK(size <= MAX_TRANSFER);
struct DataNotice * notice = &rec.data[rec.dataN++];
notice->request = request;
notice->type = type;
notice->size = size;
memcpy(notice->data, data, size);
}
LG_ClipboardResult lgClipboard_dataBegin(LG_ClipboardRequest request,
LG_ClipboardData type, uint64_t sizeHint)
{
struct StreamNotice * stream = &rec.streamData;
++stream->beginN;
stream->request = request;
stream->type = type;
stream->sizeHint = sizeHint;
return rec.beginResult;
}
LG_ClipboardResult lgClipboard_dataChunk(LG_ClipboardRequest request,
uint64_t offset, const void * data, size_t size)
{
const LG_ClipboardResult result = rec.chunkResult;
struct StreamNotice * stream = &rec.streamData;
++stream->chunkN;
CHECK(request == stream->request);
CHECK(offset == stream->size);
CHECK(data);
CHECK(size);
CHECK(size <= sizeof(stream->data) - stream->size);
if (rec.readyDuringChunk)
{
rec.readyDuringChunk = false;
rec.chunkResult = LG_CLIPBOARD_RESULT_ACCEPTED;
waylandCBRequestReady(request);
}
if (result == LG_CLIPBOARD_RESULT_ACCEPTED)
{
memcpy(stream->data + stream->size, data, size);
stream->size += size;
}
return result;
}
LG_ClipboardResult lgClipboard_dataEnd(LG_ClipboardRequest request,
uint64_t finalSize)
{
const LG_ClipboardResult result = rec.endResult;
struct StreamNotice * stream = &rec.streamData;
++stream->endN;
CHECK(request == stream->request);
stream->finalSize = finalSize;
if (rec.readyDuringEnd)
{
rec.readyDuringEnd = false;
rec.endResult = LG_CLIPBOARD_RESULT_ACCEPTED;
waylandCBRequestReady(request);
}
return result;
}
bool lgClipboard_requestStream(LG_ClipboardData type,
const LG_ClipboardStreamOps * stream, void * opaque,
LG_ClipboardRequest * request)
{
++rec.requestN;
rec.requestType = type;
rec.requestStream = stream;
rec.requestOpaque = opaque;
rec.requestId = 9000 + rec.requestN;
if (request)
*request = rec.requestId;
if (rec.requestBeginSync)
{
rec.requestBeginResult = stream->begin(
opaque, type, LG_CLIPBOARD_SIZE_UNKNOWN);
const uint8_t byte = 0xa5;
CHECK(stream->chunk(opaque, 0, &byte, 1) ==
LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(stream->chunk(opaque, 1, &byte, 1) ==
LG_CLIPBOARD_RESULT_BLOCKED);
pollFire(rec.pollN - 1, EPOLLOUT);
}
if (rec.requestCancelSync)
stream->cancel(opaque, LG_CLIPBOARD_CANCEL_REPLACED);
return rec.requestOK;
}
bool lgClipboard_requestReady(LG_ClipboardRequest request)
{
CHECK(request == rec.requestId);
++rec.requestReadyN;
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);
return (const struct wl_data_device_listener *)proto.device.listener;
}
static struct Proxy * newOffer(void)
{
CHECK(proto.offerN < ARRAY_LENGTH(proto.offer));
struct Proxy * offer = &proto.offer[proto.offerN++];
offer->kind = PROXY_OFFER;
deviceListener()->data_offer(proto.device.data,
(struct wl_data_device *)&proto.device,
(struct wl_data_offer *)offer);
CHECK(offer->listener);
CHECK(offer->user);
return offer;
}
static struct Proxy * rawOffer(void)
{
CHECK(proto.offerN < ARRAY_LENGTH(proto.offer));
struct Proxy * offer = &proto.offer[proto.offerN++];
offer->kind = PROXY_OFFER;
return offer;
}
static void offerMime(struct Proxy * offer, const char * mime)
{
const struct wl_data_offer_listener * listener =
(const struct wl_data_offer_listener *)offer->listener;
listener->offer(offer->data, (struct wl_data_offer *)offer, mime);
}
static void selectOffer(struct Proxy * offer)
{
proto.selection = offer;
deviceListener()->selection(proto.device.data,
(struct wl_data_device *)&proto.device,
(struct wl_data_offer *)offer);
}
static const struct wl_data_source_listener * sourceListener(
struct Proxy * source)
{
CHECK(source);
CHECK(source->listener);
return (const struct wl_data_source_listener *)source->listener;
}
static void start(void)
{
memset(&wlWm, 0, sizeof(wlWm));
memset(&wlCb, 0, sizeof(wlCb));
memset(&proto, 0, sizeof(proto));
memset(&rec, 0, sizeof(rec));
proto.receiveFd = -1;
LG_LOCK_INIT(wlCb.lock);
rec.pollOK = true;
rec.requestOK = 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;
proto.manager.kind = PROXY_MANAGER;
proto.device.kind = PROXY_DEVICE;
wlWm.dataDeviceManager =
(struct wl_data_device_manager *)&proto.manager;
wlWm.seat = (struct wl_seat *)(uintptr_t)1;
wlWm.keyboardEnterSerial = 77;
CHECK(waylandCBInit());
CHECK(wlCb.dataDevice == (struct wl_data_device *)&proto.device);
CHECK(proto.device.listener);
CHECK(strstr(wlCb.lgMimetype, "application/x-looking-glass-copy;pid=") ==
wlCb.lgMimetype);
}
static void finish(void)
{
if (proto.receiveFd >= 0)
{
CHECK(close(proto.receiveFd) == 0);
proto.receiveFd = -1;
}
waylandCBFree();
CHECK(!wlCb.dataDevice);
CHECK(!wlCb.offer);
CHECK(!wlCb.dndOffer);
CHECK(!wlCb.currentRead);
CHECK(proto.device.dead);
CHECK(proto.deviceReleaseN == 1);
for (unsigned int i = 0; i < rec.pollN; ++i)
CHECK(!rec.poll[i].active);
}
static struct Proxy * textOffer(void)
{
struct Proxy * offer = newOffer();
offerMime(offer, "text/plain;charset=utf-8");
selectOffer(offer);
return offer;
}
static void checkClosed(int fd)
{
errno = 0;
CHECK(fcntl(fd, F_GETFD) == -1);
CHECK(errno == EBADF);
}
static void testMime(void)
{
start();
struct Proxy * offer = newOffer();
offerMime(offer, "text/html");
offerMime(offer, "text/ico");
offerMime(offer, "image/png");
offerMime(offer, "application/xhtml+xml");
offerMime(offer, "text/plain;charset=utf-8");
offerMime(offer, "image/x-MS-bmp");
offerMime(offer, "image/bmp");
selectOffer(offer);
CHECK(rec.noticeN == 1);
CHECK(rec.notice[0].count == 3);
CHECK(rec.notice[0].type[0] == LG_CLIPBOARD_DATA_TEXT);
CHECK(rec.notice[0].type[1] == LG_CLIPBOARD_DATA_PNG);
CHECK(rec.notice[0].type[2] == LG_CLIPBOARD_DATA_BMP);
CHECK(strcmp(wlCb.mimetypes[LG_CLIPBOARD_DATA_TEXT],
"text/plain;charset=utf-8") == 0);
CHECK(strcmp(wlCb.mimetypes[LG_CLIPBOARD_DATA_PNG], "image/png") == 0);
CHECK(strcmp(wlCb.mimetypes[LG_CLIPBOARD_DATA_BMP],
"image/x-MS-bmp") == 0);
CHECK(!offer->user);
finish();
CHECK(proto.offerDestroyN == 1);
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();
struct Proxy * local = textOffer();
CHECK(rec.noticeN == 1);
waylandCBNotice(LG_CLIPBOARD_DATA_TEXT);
struct Proxy * source = &proto.source[0];
CHECK(wlCb.selectionSource == (struct wl_data_source *)source);
CHECK(wlCb.offer == (struct wl_data_offer *)local);
CHECK(!local->dead);
/* A remote notice and its self echo must not erase the Wayland offer for a
* local clipboard generation that Windows can still request. */
const uint8_t localData[] = "local clipboard";
proto.receiveData = localData;
proto.receiveSize = sizeof(localData) - 1;
/* Losing focus before the compositor echoes our source must not release
* the remote clipboard. */
waylandCBInvalidate();
CHECK(rec.releaseN == 0);
CHECK(wlCb.selectionSource == (struct wl_data_source *)source);
CHECK(wlCb.offer == (struct wl_data_offer *)local);
CHECK(!local->dead);
struct Proxy * echo = newOffer();
offerMime(echo, "text/plain");
offerMime(echo, wlCb.lgMimetype);
selectOffer(echo);
CHECK(rec.noticeN == 1);
CHECK(rec.releaseN == 0);
CHECK(wlCb.selectionSource == (struct wl_data_source *)source);
CHECK(wlCb.offer == (struct wl_data_offer *)local);
CHECK(wlCb.mimetypes[LG_CLIPBOARD_DATA_TEXT]);
CHECK(!local->dead);
CHECK(echo->dead);
CHECK(proto.offerDestroyN == 1);
CHECK(!echo->user);
waylandCBInvalidate();
CHECK(rec.releaseN == 0);
CHECK(wlCb.selectionSource == (struct wl_data_source *)source);
CHECK(wlCb.offer == (struct wl_data_offer *)local);
waylandCBRequest(102, LG_CLIPBOARD_DATA_TEXT);
CHECK(proto.receiveN == 1);
CHECK(rec.pollN == 1);
pollFire(0, EPOLLIN);
pollFire(0, EPOLLIN);
CHECK(rec.streamData.request == 102);
CHECK(rec.streamData.size == sizeof(localData) - 1);
CHECK(rec.streamData.endN == 1);
CHECK(memcmp(rec.streamData.data, localData,
sizeof(localData) - 1) == 0);
int fds[2];
CHECK(pipe(fds) == 0);
sourceListener(source)->send(source->data,
(struct wl_data_source *)source, "text/plain", fds[1]);
CHECK(rec.requestN == 1);
CHECK(rec.requestType == LG_CLIPBOARD_DATA_TEXT);
CHECK(rec.requestStream);
CHECK(rec.requestOpaque);
CHECK(rec.requestStream->begin(rec.requestOpaque,
LG_CLIPBOARD_DATA_TEXT, LG_CLIPBOARD_SIZE_UNKNOWN) ==
LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(rec.requestStream->end(rec.requestOpaque, 0) ==
LG_CLIPBOARD_RESULT_ACCEPTED);
uint8_t value;
CHECK(read(fds[0], &value, 1) == 0);
CHECK(close(fds[0]) == 0);
checkClosed(fds[1]);
selectOffer(NULL);
CHECK(rec.releaseN == 1);
CHECK(!wlCb.selectionSource);
CHECK(local->dead);
CHECK(proto.offerDestroyN == 2);
CHECK(proto.dirtyDestroyN == 0);
finish();
}
static void testLateSelfCopy(void)
{
start();
waylandCBNotice(LG_CLIPBOARD_DATA_TEXT);
struct Proxy * source = &proto.source[0];
CHECK(wlCb.selectionSource == (struct wl_data_source *)source);
struct Proxy * local = textOffer();
CHECK(rec.noticeN == 1);
CHECK(!wlCb.selectionSource);
CHECK(wlCb.offer == (struct wl_data_offer *)local);
CHECK(!local->dead);
/* The compositor may deliver our marked echo after a newer external
* selection. The stale echo must not discard that local selection. */
struct Proxy * echo = newOffer();
offerMime(echo, "text/plain");
offerMime(echo, wlCb.lgMimetype);
selectOffer(echo);
CHECK(rec.noticeN == 1);
CHECK(rec.releaseN == 0);
CHECK(!wlCb.selectionSource);
CHECK(wlCb.offer == (struct wl_data_offer *)local);
CHECK(wlCb.mimetypes[LG_CLIPBOARD_DATA_TEXT]);
CHECK(!local->dead);
CHECK(echo->dead);
const uint8_t data[] = "new local selection";
proto.receiveData = data;
proto.receiveSize = sizeof(data) - 1;
waylandCBRequest(102, LG_CLIPBOARD_DATA_TEXT);
CHECK(proto.receiveN == 1);
CHECK(rec.pollN == 1);
pollFire(0, EPOLLIN);
pollFire(0, EPOLLIN);
CHECK(rec.streamData.request == 102);
CHECK(rec.streamData.size == sizeof(data) - 1);
CHECK(rec.streamData.endN == 1);
CHECK(memcmp(rec.streamData.data, data, sizeof(data) - 1) == 0);
CHECK(proto.dirtyDestroyN == 0);
finish();
}
static void testIgnoredOffer(void)
{
start();
struct Proxy * offer = newOffer();
offerMime(offer, "application/x-unsupported");
selectOffer(offer);
CHECK(rec.noticeN == 0);
CHECK(rec.releaseN == 1);
CHECK(offer->dead);
CHECK(proto.offerDestroyN == 1);
CHECK(!offer->user);
CHECK(proto.dirtyDestroyN == 0);
finish();
}
static void testInvalidateSerialization(void)
{
start();
struct Proxy * offer = textOffer();
rec.expectReleaseLocked = true;
waylandCBInvalidate();
CHECK(rec.releaseN == 1);
CHECK(rec.releaseLockedN == 1);
CHECK(offer->dead);
CHECK(!wlCb.offer);
finish();
}
static void testReplace(void)
{
start();
struct Proxy * first = textOffer();
waylandCBRequest(101, LG_CLIPBOARD_DATA_TEXT);
CHECK(rec.pollN == 1);
const int fd = rec.poll[0].fd;
struct Proxy * second = newOffer();
offerMime(second, "image/png");
selectOffer(second);
CHECK(first->dead);
CHECK(!second->dead);
CHECK(rec.abortN == 1);
CHECK(rec.abort[0] == 101);
CHECK(rec.pollUnregisterN == 1);
CHECK(rec.pollCleanupN == 1);
CHECK(!rec.poll[0].active);
checkClosed(fd);
CHECK(rec.noticeN == 2);
CHECK(rec.notice[1].count == 1);
CHECK(rec.notice[1].type[0] == LG_CLIPBOARD_DATA_PNG);
selectOffer(NULL);
CHECK(second->dead);
CHECK(rec.releaseN == 1);
CHECK(proto.offerDestroyN == 2);
finish();
CHECK(proto.dirtyDestroyN == 0);
}
static void testReadEOF(void)
{
start();
textOffer();
uint8_t data[4096];
for (size_t i = 0; i < sizeof(data); ++i)
data[i] = i;
proto.receiveData = data;
proto.receiveSize = sizeof(data);
waylandCBRequest(201, LG_CLIPBOARD_DATA_TEXT);
CHECK(proto.receiveN == 1);
CHECK(strcmp(proto.receiveMime, "text/plain;charset=utf-8") == 0);
CHECK(rec.pollN == 1);
CHECK(rec.poll[0].events == EPOLLIN);
const int fd = rec.poll[0].fd;
pollFire(0, EPOLLIN);
CHECK(rec.poll[0].active);
CHECK(rec.streamData.beginN == 1);
CHECK(rec.streamData.chunkN == 1);
CHECK(rec.streamData.endN == 0);
pollFire(0, EPOLLIN);
CHECK(!rec.poll[0].active);
CHECK(rec.pollCleanupN == 1);
CHECK(rec.streamData.request == 201);
CHECK(rec.streamData.type == LG_CLIPBOARD_DATA_TEXT);
CHECK(rec.streamData.sizeHint == LG_CLIPBOARD_SIZE_UNKNOWN);
CHECK(rec.streamData.size == sizeof(data));
CHECK(rec.streamData.finalSize == sizeof(data));
CHECK(rec.streamData.endN == 1);
CHECK(memcmp(rec.streamData.data, data, sizeof(data)) == 0);
checkClosed(fd);
finish();
CHECK(rec.abortN == 0);
}
static void testReadBlocked(void)
{
start();
textOffer();
const uint8_t data[] = "blocked stream";
proto.receiveData = data;
proto.receiveSize = sizeof(data) - 1;
rec.chunkResult = LG_CLIPBOARD_RESULT_BLOCKED;
waylandCBRequest(202, LG_CLIPBOARD_DATA_TEXT);
CHECK(rec.pollN == 1);
pollFire(0, EPOLLIN);
CHECK(rec.streamData.beginN == 1);
CHECK(rec.streamData.chunkN == 1);
CHECK(rec.streamData.size == 0);
CHECK(rec.poll[0].events == 0);
rec.chunkResult = LG_CLIPBOARD_RESULT_ACCEPTED;
waylandCBRequestReady(202);
CHECK(rec.streamData.chunkN == 2);
CHECK(rec.streamData.size == sizeof(data) - 1);
CHECK(rec.streamData.endN == 1);
CHECK(rec.streamData.finalSize == sizeof(data) - 1);
CHECK(memcmp(rec.streamData.data, data, sizeof(data) - 1) == 0);
CHECK(!rec.poll[0].active);
CHECK(rec.abortN == 0);
finish();
}
static void testReadBlockedEmpty(void)
{
start();
textOffer();
const uint8_t data[] = "buffered while source stays open";
proto.receiveData = data;
proto.receiveSize = sizeof(data) - 1;
proto.keepReceiveOpen = true;
rec.chunkResult = LG_CLIPBOARD_RESULT_BLOCKED;
waylandCBRequest(204, LG_CLIPBOARD_DATA_TEXT);
CHECK(rec.pollN == 1);
pollFire(0, EPOLLIN);
CHECK(rec.streamData.chunkN == 1);
CHECK(rec.streamData.size == 0);
CHECK(rec.poll[0].events == 0);
/* The read drained the pipe, whose duplicated write end remains open.
* Ready must retry the buffered chunk without another poll callback. */
rec.chunkResult = LG_CLIPBOARD_RESULT_ACCEPTED;
waylandCBRequestReady(204);
CHECK(rec.streamData.chunkN == 2);
CHECK(rec.streamData.size == sizeof(data) - 1);
CHECK(memcmp(rec.streamData.data, data, sizeof(data) - 1) == 0);
CHECK(rec.streamData.endN == 0);
CHECK(rec.poll[0].active);
CHECK(rec.poll[0].events == EPOLLIN);
CHECK(close(proto.receiveFd) == 0);
proto.receiveFd = -1;
pollFire(0, EPOLLIN);
CHECK(rec.streamData.endN == 1);
CHECK(rec.streamData.finalSize == sizeof(data) - 1);
CHECK(!rec.poll[0].active);
CHECK(rec.abortN == 0);
finish();
}
static void testReadReadyRace(void)
{
start();
textOffer();
const uint8_t data[] = "ready while returning blocked";
proto.receiveData = data;
proto.receiveSize = sizeof(data) - 1;
rec.chunkResult = LG_CLIPBOARD_RESULT_BLOCKED;
rec.readyDuringChunk = true;
waylandCBRequest(203, LG_CLIPBOARD_DATA_TEXT);
CHECK(rec.pollN == 1);
pollFire(0, EPOLLIN);
CHECK(rec.streamData.beginN == 1);
CHECK(rec.streamData.chunkN == 2);
CHECK(rec.streamData.size == sizeof(data) - 1);
CHECK(rec.poll[0].events == EPOLLIN);
rec.endResult = LG_CLIPBOARD_RESULT_BLOCKED;
rec.readyDuringEnd = true;
pollFire(0, EPOLLIN);
CHECK(rec.streamData.endN == 2);
CHECK(rec.streamData.finalSize == sizeof(data) - 1);
CHECK(!rec.poll[0].active);
CHECK(rec.abortN == 0);
finish();
}
static void testReadError(void)
{
start();
textOffer();
waylandCBRequest(301, LG_CLIPBOARD_DATA_TEXT);
CHECK(rec.pollN == 1);
const int firstFd = rec.poll[0].fd;
CHECK(close(firstFd) == 0);
pollFire(0, EPOLLIN);
CHECK(rec.abortN == 1);
CHECK(rec.abort[0] == 301);
CHECK(rec.pollCleanupN == 1);
checkClosed(firstFd);
waylandCBRequest(302, LG_CLIPBOARD_DATA_TEXT);
CHECK(rec.pollN == 2);
const int secondFd = rec.poll[1].fd;
pollFire(1, EPOLLERR);
CHECK(rec.abortN == 2);
CHECK(rec.abort[1] == 302);
CHECK(rec.pollCleanupN == 2);
checkClosed(secondFd);
finish();
}
static void testSource(void)
{
start();
waylandCBNotice(LG_CLIPBOARD_DATA_TEXT);
CHECK(proto.sourceN == 1);
struct Proxy * source = &proto.source[0];
CHECK(proto.selection == source);
CHECK(proto.serial == 77);
CHECK(source->mimeN == 6);
CHECK(strcmp(source->mime[0], "text/plain") == 0);
CHECK(strcmp(source->mime[1], "text/plain;charset=utf-8") == 0);
CHECK(strcmp(source->mime[4], "UTF8_STRING") == 0);
CHECK(strcmp(source->mime[5], wlCb.lgMimetype) == 0);
int bad[2];
CHECK(pipe(bad) == 0);
sourceListener(source)->send(source->data,
(struct wl_data_source *)source, "image/png", bad[1]);
CHECK(rec.pollN == 0);
uint8_t value;
CHECK(read(bad[0], &value, 1) == 0);
CHECK(close(bad[0]) == 0);
int good[2];
CHECK(pipe(good) == 0);
sourceListener(source)->send(source->data,
(struct wl_data_source *)source, "text/plain", good[1]);
CHECK(rec.pollN == 1);
CHECK(rec.poll[0].fd == good[1]);
CHECK(rec.poll[0].events == 0);
CHECK(rec.requestN == 1);
CHECK(rec.requestType == LG_CLIPBOARD_DATA_TEXT);
CHECK(rec.requestStream);
CHECK(rec.requestOpaque);
const uint8_t first[] = "source ";
const uint8_t second[] = "data";
CHECK(rec.requestStream->begin(rec.requestOpaque,
LG_CLIPBOARD_DATA_TEXT, LG_CLIPBOARD_SIZE_UNKNOWN) ==
LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(rec.requestStream->chunk(rec.requestOpaque, 0,
first, sizeof(first) - 1) == LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(rec.poll[0].events == EPOLLOUT);
CHECK(rec.requestStream->chunk(rec.requestOpaque, sizeof(first) - 1,
second, sizeof(second) - 1) == LG_CLIPBOARD_RESULT_BLOCKED);
pollFire(0, EPOLLOUT);
CHECK(rec.requestReadyN == 1);
CHECK(rec.poll[0].events == 0);
CHECK(rec.requestStream->chunk(rec.requestOpaque, sizeof(first) - 1,
second, sizeof(second) - 1) == LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(rec.requestStream->end(rec.requestOpaque,
sizeof(first) + sizeof(second) - 2) == LG_CLIPBOARD_RESULT_BLOCKED);
pollFire(0, EPOLLOUT);
CHECK(rec.requestReadyN == 2);
CHECK(rec.requestStream->end(rec.requestOpaque,
sizeof(first) + sizeof(second) - 2) == LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(!rec.poll[0].active);
sourceListener(source)->cancelled(source->data,
(struct wl_data_source *)source);
CHECK(source->dead);
CHECK(proto.sourceDestroyN == 1);
CHECK(rec.pollCleanupN == 1);
uint8_t actual[sizeof(first) + sizeof(second)] = {};
const size_t expected = sizeof(first) + sizeof(second) - 2;
CHECK(read(good[0], actual, sizeof(actual)) == (ssize_t)expected);
CHECK(memcmp(actual, "source data", expected) == 0);
CHECK(read(good[0], &value, 1) == 0);
CHECK(close(good[0]) == 0);
checkClosed(good[1]);
finish();
}
static void testSourceRelease(void)
{
start();
waylandCBNotice(LG_CLIPBOARD_DATA_TEXT);
struct Proxy * source = &proto.source[0];
CHECK(wlCb.selectionSource == (struct wl_data_source *)source);
CHECK(wlCb.sources);
CHECK(proto.selection == source);
CHECK(proto.selectionSetN == 1);
waylandCBRelease();
CHECK(!wlCb.selectionSource);
CHECK(wlCb.sources);
CHECK(!proto.selection);
CHECK(proto.selectionSetN == 2);
waylandCBRelease();
CHECK(proto.selectionSetN == 2);
sourceListener(source)->cancelled(source->data,
(struct wl_data_source *)source);
CHECK(source->dead);
CHECK(!wlCb.sources);
finish();
}
static void testSourceExternalOwner(void)
{
start();
waylandCBNotice(LG_CLIPBOARD_DATA_TEXT);
struct Proxy * source = &proto.source[0];
CHECK(proto.selectionSetN == 1);
struct Proxy * external = newOffer();
offerMime(external, "image/png");
selectOffer(external);
CHECK(!wlCb.selectionSource);
CHECK(proto.selection == external);
waylandCBRelease();
CHECK(proto.selectionSetN == 1);
CHECK(proto.selection == external);
sourceListener(source)->cancelled(source->data,
(struct wl_data_source *)source);
CHECK(source->dead);
finish();
}
static void testSourceReplacement(void)
{
start();
waylandCBNotice(LG_CLIPBOARD_DATA_TEXT);
struct Proxy * first = &proto.source[0];
waylandCBNotice(LG_CLIPBOARD_DATA_PNG);
struct Proxy * second = &proto.source[1];
CHECK(proto.selection == second);
CHECK(proto.selectionSetN == 2);
CHECK(wlCb.selectionSource == (struct wl_data_source *)second);
sourceListener(first)->cancelled(first->data,
(struct wl_data_source *)first);
CHECK(first->dead);
CHECK(wlCb.selectionSource == (struct wl_data_source *)second);
waylandCBRelease();
CHECK(!proto.selection);
CHECK(proto.selectionSetN == 3);
CHECK(!wlCb.selectionSource);
sourceListener(second)->cancelled(second->data,
(struct wl_data_source *)second);
CHECK(second->dead);
CHECK(!wlCb.sources);
finish();
}
static void testSourceCancelDuringRequest(void)
{
start();
waylandCBNotice(LG_CLIPBOARD_DATA_TEXT);
struct Proxy * source = &proto.source[0];
rec.requestCancelSync = true;
int fds[2];
CHECK(pipe(fds) == 0);
sourceListener(source)->send(source->data,
(struct wl_data_source *)source, "text/plain", fds[1]);
CHECK(rec.requestN == 1);
CHECK(rec.pollN == 1);
CHECK(!rec.poll[0].active);
CHECK(rec.pollCleanupN == 1);
checkClosed(fds[1]);
uint8_t value;
CHECK(read(fds[0], &value, 1) == 0);
CHECK(close(fds[0]) == 0);
sourceListener(source)->cancelled(source->data,
(struct wl_data_source *)source);
finish();
}
static void testSourceBeginDuringRequest(void)
{
start();
waylandCBNotice(LG_CLIPBOARD_DATA_TEXT);
struct Proxy * source = &proto.source[0];
rec.requestBeginSync = true;
int fds[2];
CHECK(pipe(fds) == 0);
sourceListener(source)->send(source->data,
(struct wl_data_source *)source, "text/plain", fds[1]);
CHECK(rec.requestN == 1);
CHECK(rec.requestBeginResult == LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(rec.requestOpaque);
CHECK(rec.requestReadyN == 1);
CHECK(rec.requestStream->end(rec.requestOpaque, 1) ==
LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(rec.pollCleanupN == 1);
uint8_t value = 0;
CHECK(read(fds[0], &value, 1) == 1);
CHECK(value == 0xa5);
CHECK(read(fds[0], &value, 1) == 0);
CHECK(close(fds[0]) == 0);
checkClosed(fds[1]);
sourceListener(source)->cancelled(source->data,
(struct wl_data_source *)source);
finish();
}
static void testSourceError(void)
{
start();
waylandCBNotice(LG_CLIPBOARD_DATA_TEXT);
struct Proxy * source = &proto.source[0];
int fds[2];
CHECK(pipe(fds) == 0);
sourceListener(source)->send(source->data,
(struct wl_data_source *)source, "text/plain", fds[1]);
CHECK(rec.requestStream->begin(rec.requestOpaque,
LG_CLIPBOARD_DATA_TEXT, LG_CLIPBOARD_SIZE_UNKNOWN) ==
LG_CLIPBOARD_RESULT_ACCEPTED);
const uint8_t first[] = "first";
const uint8_t second[] = "second";
CHECK(rec.requestStream->chunk(rec.requestOpaque, 0,
first, sizeof(first) - 1) == LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(rec.requestStream->chunk(rec.requestOpaque, sizeof(first) - 1,
second, sizeof(second) - 1) == LG_CLIPBOARD_RESULT_BLOCKED);
CHECK(close(fds[0]) == 0);
pollFire(0, EPOLLERR);
CHECK(rec.requestReadyN == 1);
CHECK(!rec.poll[0].active);
CHECK(rec.pollCleanupN == 1);
CHECK(rec.requestStream->chunk(rec.requestOpaque, sizeof(first) - 1,
second, sizeof(second) - 1) == LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(rec.requestStream->end(rec.requestOpaque,
sizeof(first) + sizeof(second) - 2) == LG_CLIPBOARD_RESULT_ACCEPTED);
checkClosed(fds[1]);
sourceListener(source)->cancelled(source->data,
(struct wl_data_source *)source);
finish();
}
static void testTeardown(void)
{
start();
struct Proxy * offer = textOffer();
waylandCBRequest(401, LG_CLIPBOARD_DATA_TEXT);
CHECK(rec.pollN == 1);
const int fd = rec.poll[0].fd;
struct Proxy * dnd = rawOffer();
wlCb.dndOffer = (struct wl_data_offer *)dnd;
waylandCBNotice(LG_CLIPBOARD_DATA_TEXT);
struct Proxy * source = &proto.source[0];
finish();
CHECK(offer->dead);
CHECK(dnd->dead);
CHECK(source->dead);
CHECK(proto.offerDestroyN == 2);
CHECK(proto.sourceDestroyN == 1);
CHECK(proto.dirtyDestroyN == 0);
CHECK(proto.selectionSetN == 2);
CHECK(!proto.selection);
CHECK(rec.pollUnregisterN == 1);
CHECK(rec.pollCleanupN == 1);
checkClosed(fd);
}
static void testTeardownExternalOwner(void)
{
start();
waylandCBNotice(LG_CLIPBOARD_DATA_TEXT);
struct Proxy * source = &proto.source[0];
struct Proxy * external = newOffer();
offerMime(external, "text/plain");
selectOffer(external);
CHECK(!wlCb.selectionSource);
finish();
CHECK(source->dead);
CHECK(external->dead);
CHECK(proto.selectionSetN == 1);
CHECK(proto.selection == external);
}
static void testDnd(void)
{
start();
struct Proxy * leave = rawOffer();
wlCb.dndOffer = (struct wl_data_offer *)leave;
deviceListener()->leave(proto.device.data,
(struct wl_data_device *)&proto.device);
CHECK(!wlCb.dndOffer);
CHECK(leave->dead);
CHECK(proto.offerDestroyN == 1);
struct Proxy * drop = rawOffer();
wlCb.dndOffer = (struct wl_data_offer *)drop;
deviceListener()->drop(proto.device.data,
(struct wl_data_device *)&proto.device);
CHECK(!wlCb.dndOffer);
CHECK(drop->dead);
CHECK(proto.offerDestroyN == 2);
struct Proxy * enter = newOffer();
offerMime(enter, "text/plain");
deviceListener()->enter(proto.device.data,
(struct wl_data_device *)&proto.device, 1, wlWm.surface, 0, 0,
(struct wl_data_offer *)enter);
CHECK(wlCb.dndOffer == (struct wl_data_offer *)enter);
CHECK(!enter->user);
CHECK(proto.setActionsN == 1);
deviceListener()->leave(proto.device.data,
(struct wl_data_device *)&proto.device);
CHECK(!wlCb.dndOffer);
CHECK(enter->dead);
CHECK(proto.offerDestroyN == 3);
finish();
}
struct Test
{
const char * name;
void (*run)(void);
};
static const struct Test tests[] =
{
{ "mime" , testMime },
{ "file-import", testFileImport },
{ "file-source", testFileSource },
{ "file-replace", testFileSourceReplacement },
{ "replace" , testReplace },
{ "read-eof" , testReadEOF },
{ "read-block", testReadBlocked },
{ "read-empty", testReadBlockedEmpty },
{ "read-ready", testReadReadyRace },
{ "read-error", testReadError },
{ "source" , testSource },
{ "source-release", testSourceRelease },
{ "source-external", testSourceExternalOwner },
{ "source-replace", testSourceReplacement },
{ "source-cancel", testSourceCancelDuringRequest },
{ "source-early", testSourceBeginDuringRequest },
{ "source-error", testSourceError },
{ "teardown" , testTeardown },
{ "teardown-external", testTeardownExternalOwner },
{ "self-copy" , testSelfCopy },
{ "self-copy-late", testLateSelfCopy },
{ "ignored" , testIgnoredOffer },
{ "invalidate-lock", testInvalidateSerialization },
{ "dnd" , testDnd },
};
int main(int argc, char ** argv)
{
debug_init();
if (argc == 2)
{
for (size_t i = 0; i < ARRAY_LENGTH(tests); ++i)
if (strcmp(argv[1], tests[i].name) == 0)
{
tests[i].run();
return 0;
}
fprintf(stderr, "unknown test: %s\n", argv[1]);
return EXIT_FAILURE;
}
if (argc != 1)
return EXIT_FAILURE;
for (size_t i = 0; i < ARRAY_LENGTH(tests); ++i)
tests[i].run();
return 0;
}