Files
LookingGlass/client/tests/x11_clipboard_test.c
Geoffrey McRae 8fbe3e7454
Some checks failed
build / client (Debug, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client (Debug, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client (Debug, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client (Debug, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / client (Release, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client (Release, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client (Release, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client (Release, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / module (push) Has been cancelled
build / host-linux (push) Has been cancelled
build / host-windows-cross (push) Has been cancelled
build / host-windows-native (push) Has been cancelled
build / idd (push) Has been cancelled
build / obs (clang) (push) Has been cancelled
build / obs (gcc) (push) Has been cancelled
build / docs (push) Has been cancelled
[repos] lgprotocol: import shared protocol definitions
Add LGProtocol as a pinned submodule and consume its KVMFR protocol
definitions throughout the client, host, IDD, OBS, and profiler.

Keep Looking Glass framebuffer helpers local while removing duplicated
protocol headers and migrating users to KVMFR-scoped types.
2026-09-01 20:58:25 +10:00

1552 lines
43 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 "atoms.h"
#include "clipboard.h"
#include "test.h"
#include "x11.h"
#include "core/clipboard.h"
#include "core/clipboard_files.h"
#include "common/debug.h"
#include <LGProtocol/KVMFRClipboard.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <X11/extensions/Xfixes.h>
#define ARRAY_LENGTH(a) (sizeof(a) / sizeof((a)[0]))
#define MAX_ABORT 12U
#define MAX_CHANGE 8U
#define MAX_CONVERT 16U
#define MAX_DATA 4U
#define MAX_NOTICE 8U
#define MAX_PROP 16U
#define MAX_SEND 8U
#define MAX_TRANSFER (128U * 1024U)
#define MAX_WINDOW 16U
#define A_CLIPBOARD 10UL
#define A_TARGETS 11UL
#define A_SEL_DATA 12UL
#define A_INCR 13UL
#define A_TEXT 100UL
#define A_PNG 101UL
#define A_BMP 102UL
#define A_TIFF 103UL
#define A_JPEG 104UL
#define A_URI 105UL
#define A_GNOME 106UL
#define A_MATE 107UL
#define A_KDE 108UL
#define A_TEXT_PLAIN 109UL
#define A_TEXT_UTF8 110UL
struct WindowLog
{
Window window;
bool dead;
};
struct ConvertLog
{
Atom selection;
Atom target;
Atom property;
Window requestor;
};
struct ChangeLog
{
Window window;
Atom property;
Atom type;
int format;
int count;
unsigned char data[MAX_TRANSFER];
size_t size;
};
struct Property
{
int status;
Atom type;
int format;
unsigned long count;
unsigned long after;
unsigned char data[MAX_TRANSFER];
size_t size;
bool null;
};
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 Log
{
struct WindowLog window[MAX_WINDOW];
struct ConvertLog convert[MAX_CONVERT];
struct ChangeLog change[MAX_CHANGE];
struct Property prop[MAX_PROP];
struct TypeNotice notice[MAX_NOTICE];
struct DataNotice data[MAX_DATA];
XEvent send[MAX_SEND];
LG_ClipboardRequest abort[MAX_ABORT];
unsigned int windowN;
unsigned int convertN;
unsigned int changeN;
unsigned int propN;
unsigned int propPos;
unsigned int noticeN;
unsigned int dataN;
unsigned int sendN;
unsigned int abortN;
unsigned int releaseN;
unsigned int requestN;
unsigned int requestReadyN;
unsigned int destroyN;
unsigned int freeN;
unsigned int flushN;
unsigned int grabN;
unsigned int ungrabN;
unsigned int selectN;
unsigned int structureSelectN;
LG_ClipboardData requestType;
const LG_ClipboardStreamOps * requestStream;
void * requestOpaque;
LG_ClipboardRequest requestId;
LG_ClipboardRequest streamRequest;
LG_ClipboardData streamType;
uint64_t streamSizeHint;
unsigned int streamBeginN;
unsigned int streamChunkN;
unsigned int streamEndN;
size_t streamMaxChunk;
LG_ClipboardResult beginResult;
LG_ClipboardResult chunkResult;
LG_ClipboardResult endResult;
LG_ClipboardResult requestBeginResult;
bool readyDuringChunk;
bool readyDuringEnd;
Window owner;
Window nextWindow;
bool requestOK;
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;
struct X11DSAtoms x11atoms;
static struct Log rec;
static pthread_mutex_t readRaceLock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t readRaceCond = PTHREAD_COND_INITIALIZER;
static bool readRacePause;
static bool readRaceEntered;
static bool readRaceResume;
static size_t itemSize(int format, unsigned long count)
{
switch (format)
{
case 8:
return count;
case 16:
return count * sizeof(short);
case 32:
return count * sizeof(long);
default:
return 0;
}
}
Atom XInternAtom(Display * display, const char * name, Bool onlyIfExists)
{
CHECK(display == x11.display);
CHECK(!onlyIfExists);
if (!strcmp(name, "UTF8_STRING"))
return A_TEXT;
if (!strcmp(name, "text/plain"))
return A_TEXT_PLAIN;
if (!strcmp(name, "text/plain;charset=utf-8"))
return A_TEXT_UTF8;
if (!strcmp(name, "image/png"))
return A_PNG;
if (!strcmp(name, "image/bmp"))
return A_BMP;
if (!strcmp(name, "image/tiff"))
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, "x-special/mate-copied-files"))
return A_MATE;
if (!strcmp(name, "application/x-kde-cutselection"))
return A_KDE;
CHECK(false);
return None;
}
Bool XFixesQueryExtension(Display * display, int * eventBase, int * errorBase)
{
CHECK(display == x11.display);
if (!rec.fixesOK)
return False;
*eventBase = 80;
*errorBase = 81;
return True;
}
void XFixesSelectSelectionInput(Display * display, Window window,
Atom selection, unsigned long mask)
{
CHECK(display == x11.display);
CHECK(window == x11.window);
CHECK(selection == x11atoms.CLIPBOARD);
CHECK(mask == XFixesSetSelectionOwnerNotifyMask);
++rec.selectN;
}
Window XCreateSimpleWindow(Display * display, Window parent,
int x, int y, unsigned int width, unsigned int height,
unsigned int borderWidth, unsigned long border,
unsigned long background)
{
CHECK(display == x11.display);
CHECK(parent == x11.window);
CHECK(x == 0);
CHECK(y == 0);
CHECK(width == 1);
CHECK(height == 1);
CHECK(borderWidth == 0);
CHECK(border == 0);
CHECK(background == 0);
CHECK(rec.windowN < ARRAY_LENGTH(rec.window));
struct WindowLog * window = &rec.window[rec.windowN++];
window->window = ++rec.nextWindow;
return window->window;
}
int XDestroyWindow(Display * display, Window window)
{
CHECK(display == x11.display);
for (unsigned int i = 0; i < rec.windowN; ++i)
if (rec.window[i].window == window)
{
CHECK(!rec.window[i].dead);
rec.window[i].dead = true;
++rec.destroyN;
return Success;
}
CHECK(false);
return BadWindow;
}
int XConvertSelection(Display * display, Atom selection, Atom target,
Atom property, Window requestor, Time time)
{
CHECK(display == x11.display);
CHECK(time == CurrentTime);
CHECK(rec.convertN < ARRAY_LENGTH(rec.convert));
struct ConvertLog * convert = &rec.convert[rec.convertN++];
convert->selection = selection;
convert->target = target;
convert->property = property;
convert->requestor = requestor;
return Success;
}
int XSelectInput(Display * display, Window window, long mask)
{
CHECK(display == x11.display);
CHECK(mask == PropertyChangeMask ||
mask == (PropertyChangeMask | StructureNotifyMask));
if (mask & StructureNotifyMask)
++rec.structureSelectN;
CHECK(window != None);
return Success;
}
int XSetSelectionOwner(Display * display, Atom selection,
Window owner, Time time)
{
CHECK(display == x11.display);
CHECK(selection == x11atoms.CLIPBOARD);
CHECK(time == CurrentTime);
rec.owner = owner;
return Success;
}
Window XGetSelectionOwner(Display * display, Atom selection)
{
CHECK(display == x11.display);
CHECK(selection == x11atoms.CLIPBOARD);
return rec.owner;
}
int XGrabServer(Display * display)
{
CHECK(display == x11.display);
++rec.grabN;
return Success;
}
int XUngrabServer(Display * display)
{
CHECK(display == x11.display);
++rec.ungrabN;
return Success;
}
int XFlush(Display * display)
{
CHECK(display == x11.display);
++rec.flushN;
return Success;
}
int XChangeProperty(Display * display, Window window, Atom property,
Atom type, int format, int mode, const unsigned char * data, int count)
{
CHECK(display == x11.display);
CHECK(mode == PropModeReplace);
CHECK(count >= 0);
CHECK(rec.changeN < ARRAY_LENGTH(rec.change));
struct ChangeLog * change = &rec.change[rec.changeN++];
change->window = window;
change->property = property;
change->type = type;
change->format = format;
change->count = count;
change->size = itemSize(format, count);
CHECK(change->size <= sizeof(change->data));
CHECK(!change->size || data);
if (change->size)
memcpy(change->data, data, change->size);
return Success;
}
Status XSendEvent(Display * display, Window window, Bool propagate,
long mask, XEvent * event)
{
CHECK(display == x11.display);
CHECK(!propagate);
CHECK(mask == 0);
CHECK(rec.sendN < ARRAY_LENGTH(rec.send));
CHECK(event->xselection.requestor == window);
rec.send[rec.sendN++] = *event;
return True;
}
int XGetWindowProperty(Display * display, Window window, Atom property,
long offset, long length, Bool delete, Atom requestedType,
Atom * actualType, int * actualFormat, unsigned long * count,
unsigned long * after, unsigned char ** data)
{
CHECK(display == x11.display);
CHECK(window != None);
CHECK(property != None);
CHECK(offset >= 0);
CHECK(length == ~0L || length ==
(KVMFR_CLIPBOARD_REPRESENTATION_BYTES + 3U) / 4U);
CHECK(delete);
CHECK(requestedType == AnyPropertyType);
CHECK(rec.propPos < rec.propN);
const struct Property * prop = &rec.prop[rec.propPos];
*actualType = prop->type;
*actualFormat = prop->format;
size_t start = 0;
size_t size = prop->size;
if (prop->format == 8 && length != ~0L)
{
start = (size_t)offset * 4U;
CHECK(start <= prop->size);
const size_t maximum = (size_t)length * 4U;
size = prop->size - start;
if (size > maximum)
size = maximum;
*count = size;
*after = prop->size - start - size;
}
else
{
*count = prop->count;
*after = prop->after;
}
if (prop->null)
*data = NULL;
else
{
CHECK(start + size <= sizeof(prop->data));
*data = malloc(size ? size : 1);
CHECK(*data);
if (size)
memcpy(*data, prop->data + start, size);
}
if (*after == 0)
++rec.propPos;
return prop->status;
}
int XFree(void * data)
{
CHECK(data);
++rec.freeN;
free(data);
return Success;
}
void clipboard_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++];
notice->count = count;
memcpy(notice->type, types, count * sizeof(*types));
}
void clipboard_data(LG_ClipboardRequest request,
LG_ClipboardData type, const void * data, size_t size)
{
CHECK(rec.dataN < ARRAY_LENGTH(rec.data));
CHECK(size <= MAX_TRANSFER);
CHECK(!size || data);
struct DataNotice * notice = &rec.data[rec.dataN++];
notice->request = request;
notice->type = type;
notice->size = size;
if (size)
memcpy(notice->data, data, size);
}
LG_ClipboardResult clipboard_dataBegin(LG_ClipboardRequest request,
LG_ClipboardData type, uint64_t sizeHint)
{
++rec.streamBeginN;
rec.streamRequest = request;
rec.streamType = type;
rec.streamSizeHint = sizeHint;
if (rec.beginResult == LG_CLIPBOARD_RESULT_ACCEPTED)
{
CHECK(rec.dataN < ARRAY_LENGTH(rec.data));
rec.data[rec.dataN] = (struct DataNotice)
{
.request = request,
.type = type,
};
}
return rec.beginResult;
}
LG_ClipboardResult clipboard_dataChunk(LG_ClipboardRequest request,
uint64_t offset, const void * data, size_t size)
{
const LG_ClipboardResult result = rec.chunkResult;
++rec.streamChunkN;
CHECK(request == rec.streamRequest);
CHECK(rec.dataN < ARRAY_LENGTH(rec.data));
struct DataNotice * notice = &rec.data[rec.dataN];
CHECK(offset == notice->size);
CHECK(data);
CHECK(size);
CHECK(size <= sizeof(notice->data) - notice->size);
if (size > rec.streamMaxChunk)
rec.streamMaxChunk = size;
pthread_mutex_lock(&readRaceLock);
if (readRacePause)
{
readRacePause = false;
readRaceEntered = true;
pthread_cond_signal(&readRaceCond);
while (!readRaceResume)
pthread_cond_wait(&readRaceCond, &readRaceLock);
}
pthread_mutex_unlock(&readRaceLock);
if (rec.readyDuringChunk)
{
rec.readyDuringChunk = false;
rec.chunkResult = LG_CLIPBOARD_RESULT_ACCEPTED;
x11CBRequestReady(request);
}
if (result == LG_CLIPBOARD_RESULT_ACCEPTED)
{
memcpy(notice->data + notice->size, data, size);
notice->size += size;
}
return result;
}
LG_ClipboardResult clipboard_dataEnd(LG_ClipboardRequest request,
uint64_t finalSize)
{
const LG_ClipboardResult result = rec.endResult;
++rec.streamEndN;
CHECK(request == rec.streamRequest);
CHECK(rec.dataN < ARRAY_LENGTH(rec.data));
if (rec.readyDuringEnd)
{
rec.readyDuringEnd = false;
rec.endResult = LG_CLIPBOARD_RESULT_ACCEPTED;
x11CBRequestReady(request);
}
if (result == LG_CLIPBOARD_RESULT_ACCEPTED)
{
CHECK(finalSize == rec.data[rec.dataN].size);
++rec.dataN;
}
return result;
}
void clipboard_abort(LG_ClipboardRequest request)
{
CHECK(rec.abortN < ARRAY_LENGTH(rec.abort));
rec.abort[rec.abortN++] = request;
}
void clipboard_release(void)
{
++rec.releaseN;
}
bool clipboard_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);
if (rec.requestCancelSync)
stream->cancel(opaque, LG_CLIPBOARD_CANCEL_REPLACED);
return rec.requestOK;
}
bool clipboard_requestReady(LG_ClipboardRequest request)
{
CHECK(request == rec.requestId);
++rec.requestReadyN;
return true;
}
bool clipboardFiles_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 clipboardFiles_clearLocal(void)
{
++rec.fileClearN;
}
uint64_t clipboardFiles_remotePresentationAcquire(void)
{
++rec.presentationAcquireN;
return rec.presentationOK ? 2001U : 0;
}
bool clipboardFiles_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") ||
!strcmp(mime, "x-special/mate-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 clipboardFiles_remotePresentationDelivered(uint64_t presentation)
{
CHECK(presentation);
CHECK(rec.presentationDeliveredN <
ARRAY_LENGTH(rec.presentationDelivered));
rec.presentationDelivered[rec.presentationDeliveredN++] = presentation;
}
void clipboardFiles_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));
struct Property * prop = &rec.prop[rec.propN++];
prop->status = Success;
return prop;
}
static void prop8(Atom type, const void * data, size_t size)
{
struct Property * value = prop();
CHECK(size <= sizeof(value->data));
value->type = type;
value->format = 8;
value->count = size;
value->size = size;
if (size)
memcpy(value->data, data, size);
}
static void prop32(Atom type, const unsigned long * data, size_t count)
{
struct Property * value = prop();
const size_t size = count * sizeof(*data);
CHECK(size <= sizeof(value->data));
value->type = type;
value->format = 32;
value->count = count;
value->size = size;
if (size)
memcpy(value->data, data, size);
}
static void propNull(Atom type, int format, unsigned long count)
{
struct Property * value = prop();
value->type = type;
value->format = format;
value->count = count;
value->null = true;
}
static void selection(Window requestor, Atom target, Atom property)
{
XEvent event = {};
event.xselection.type = SelectionNotify;
event.xselection.display = x11.display;
event.xselection.requestor = requestor;
event.xselection.selection = x11atoms.CLIPBOARD;
event.xselection.target = target;
event.xselection.property = property;
CHECK(x11CBEventThread(&event));
}
static void propertyState(Window window, Atom atom, int state)
{
XEvent event = {};
event.xproperty.type = PropertyNotify;
event.xproperty.display = x11.display;
event.xproperty.window = window;
event.xproperty.atom = atom;
event.xproperty.state = state;
CHECK(x11CBEventThread(&event));
}
static void property(Window window)
{
propertyState(window, x11atoms.SEL_DATA, PropertyNewValue);
}
static void owner(Window owner)
{
rec.owner = owner;
XFixesSelectionNotifyEvent event =
{
.type = x11.eventBase + XFixesSelectionNotify,
.display = x11.display,
.window = x11.window,
.owner = owner,
.selection = x11atoms.CLIPBOARD,
};
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)
{
const unsigned int no = rec.convertN;
owner(ownerWindow);
CHECK(rec.convertN == no + 1);
const struct ConvertLog * convert = &rec.convert[no];
CHECK(convert->selection == x11atoms.CLIPBOARD);
CHECK(convert->target == x11atoms.TARGETS);
CHECK(convert->property == x11atoms.TARGETS);
prop32(XA_ATOM, targets, count);
selection(convert->requestor, x11atoms.TARGETS, x11atoms.TARGETS);
}
static Window request(LG_ClipboardRequest id, LG_ClipboardData type)
{
const unsigned int no = rec.convertN;
x11CBRequest(id, type);
CHECK(rec.convertN == no + 1);
const struct ConvertLog * convert = &rec.convert[no];
CHECK(convert->selection == x11atoms.CLIPBOARD);
CHECK(convert->property == x11atoms.SEL_DATA);
return convert->requestor;
}
static void selectionRequest(Atom target, Atom property)
{
XEvent event = {};
event.xselectionrequest.type = SelectionRequest;
event.xselectionrequest.display = x11.display;
event.xselectionrequest.owner = x11.window;
event.xselectionrequest.requestor = 900;
event.xselectionrequest.selection = x11atoms.CLIPBOARD;
event.xselectionrequest.target = target;
event.xselectionrequest.property = property;
CHECK(x11CBEventThread(&event));
}
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.fileSetOK = true;
rec.presentationOK = true;
pthread_mutex_lock(&readRaceLock);
readRacePause = false;
readRaceEntered = false;
readRaceResume = false;
pthread_mutex_unlock(&readRaceLock);
x11.display = (Display *)(uintptr_t)1;
x11.window = 50;
x11atoms.CLIPBOARD = A_CLIPBOARD;
x11atoms.TARGETS = A_TARGETS;
x11atoms.SEL_DATA = A_SEL_DATA;
x11atoms.INCR = A_INCR;
CHECK(x11CBInit());
CHECK(x11.eventBase == 80);
CHECK(x11.errorBase == 81);
CHECK(rec.selectN == 1);
}
static void finish(void)
{
x11CBRelease();
owner(None);
CHECK(rec.owner == None);
CHECK(rec.grabN == rec.ungrabN);
for (unsigned int i = 0; i < rec.windowN; ++i)
CHECK(rec.window[i].dead);
}
static void testTargets(void)
{
start();
const unsigned long targets[] = { A_PNG, 999, A_TEXT, A_PNG };
discover(60, targets, ARRAY_LENGTH(targets));
CHECK(rec.noticeN == 1);
CHECK(rec.notice[0].count == 2);
CHECK(rec.notice[0].type[0] == LG_CLIPBOARD_DATA_TEXT);
CHECK(rec.notice[0].type[1] == LG_CLIPBOARD_DATA_PNG);
CHECK(rec.destroyN == 1);
CHECK(rec.freeN == 1);
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 testFileImportMate(void)
{
start();
const unsigned long targets[] = { A_TEXT, A_URI, A_MATE };
discover(603, targets, ARRAY_LENGTH(targets));
CHECK(rec.convertN == 2);
const struct ConvertLog * convert = &rec.convert[1];
CHECK(convert->target == A_MATE);
CHECK(convert->property == x11atoms.SEL_DATA);
const char payload[] =
"copy\nfile:///home/user/first.txt\r\n"
"file:///home/user/second.txt\r\n";
prop8(A_MATE, payload, sizeof(payload) - 1U);
selection(convert->requestor, A_MATE, x11atoms.SEL_DATA);
CHECK(rec.fileSetN == 1);
CHECK(strcmp(rec.fileMime, "x-special/mate-copied-files") == 0);
CHECK(rec.fileSize == sizeof(payload) - 1U);
CHECK(memcmp(rec.fileData, payload, sizeof(payload) - 1U) == 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 == 5);
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_MATE);
CHECK(targets[4] == 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 testFileSourceMate(void)
{
start();
x11CBNotice(LG_CLIPBOARD_DATA_FILES);
CHECK(rec.presentationAcquireN == 1);
CHECK(rec.presentationReleaseN == 0);
selectionRequest(A_MATE, 713);
CHECK(rec.presentationAcquireN == 2);
CHECK(rec.structureSelectN == 1);
CHECK(rec.changeN == 1);
CHECK(rec.change[0].type == x11atoms.INCR);
CHECK(rec.change[0].format == 32);
CHECK(rec.change[0].count == 1);
CHECK(rec.sendN == 1);
propertyState(900, 713, PropertyDelete);
CHECK(rec.changeN == 2);
CHECK(rec.change[1].type == A_MATE);
CHECK(rec.change[1].format == 8);
const char expected[] =
"copy\nfile:///run/user/1000/looking-glass/guest.txt\r\n";
CHECK(rec.change[1].size == sizeof(expected) - 1U);
CHECK(memcmp(rec.change[1].data,
expected, sizeof(expected) - 1U) == 0);
propertyState(900, 713, PropertyDelete);
CHECK(rec.changeN == 3);
CHECK(rec.change[2].type == A_MATE);
CHECK(rec.change[2].count == 0);
CHECK(rec.presentationDeliveredN == 1);
CHECK(rec.presentationDelivered[0] == 2001U);
CHECK(rec.presentationReleaseN == 1);
CHECK(rec.presentationReleased[0] == 2001U);
finish();
CHECK(rec.presentationReleaseN == 2);
CHECK(rec.presentationReleased[1] == 2001U);
}
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();
const unsigned long targets[] = { A_TEXT };
discover(61, targets, ARRAY_LENGTH(targets));
const Window window = request(101, LG_CLIPBOARD_DATA_TEXT);
const uint8_t data[] = "normal data";
prop8(A_TEXT, data, sizeof(data) - 1);
selection(window, A_TEXT, x11atoms.SEL_DATA);
CHECK(rec.dataN == 1);
CHECK(rec.data[0].request == 101);
CHECK(rec.data[0].type == LG_CLIPBOARD_DATA_TEXT);
CHECK(rec.data[0].size == sizeof(data) - 1);
CHECK(memcmp(rec.data[0].data, data, sizeof(data) - 1) == 0);
CHECK(rec.abortN == 0);
CHECK(rec.destroyN == 2);
CHECK(rec.freeN == 2);
finish();
}
static void testIncr(void)
{
start();
const unsigned long targets[] = { A_TEXT };
discover(62, targets, ARRAY_LENGTH(targets));
const Window window = request(201, LG_CLIPBOARD_DATA_TEXT);
const unsigned long capacity = 2;
prop32(x11atoms.INCR, &capacity, 1);
selection(window, A_TEXT, x11atoms.SEL_DATA);
CHECK(rec.dataN == 0);
CHECK(rec.abortN == 0);
const uint8_t first[] = "abc";
const uint8_t second[] = "defg";
prop8(A_TEXT, first, sizeof(first) - 1);
property(window);
prop8(A_TEXT, second, sizeof(second) - 1);
property(window);
propNull(A_TEXT, 8, 0);
property(window);
CHECK(rec.dataN == 1);
CHECK(rec.data[0].request == 201);
CHECK(rec.data[0].type == LG_CLIPBOARD_DATA_TEXT);
CHECK(rec.data[0].size == 7);
CHECK(memcmp(rec.data[0].data, "abcdefg", 7) == 0);
CHECK(rec.abortN == 0);
CHECK(rec.destroyN == 2);
CHECK(rec.freeN == 4);
finish();
}
static void testIncrBlocked(void)
{
start();
const unsigned long targets[] = { A_TEXT };
discover(620, targets, ARRAY_LENGTH(targets));
const Window window = request(211, LG_CLIPBOARD_DATA_TEXT);
const unsigned long capacity = 9;
rec.beginResult = LG_CLIPBOARD_RESULT_BLOCKED;
prop32(x11atoms.INCR, &capacity, 1);
selection(window, A_TEXT, x11atoms.SEL_DATA);
CHECK(rec.streamBeginN == 1);
CHECK(rec.dataN == 0);
const uint8_t first[] = "abc";
prop8(A_TEXT, first, sizeof(first) - 1);
property(window);
CHECK(rec.streamChunkN == 0);
rec.beginResult = LG_CLIPBOARD_RESULT_ACCEPTED;
rec.chunkResult = LG_CLIPBOARD_RESULT_BLOCKED;
x11CBRequestReady(211);
CHECK(rec.streamBeginN == 2);
CHECK(rec.streamSizeHint == capacity);
CHECK(rec.streamChunkN == 1);
CHECK(rec.data[0].size == 0);
const uint8_t second[] = "defghi";
prop8(A_TEXT, second, sizeof(second) - 1);
property(window);
CHECK(rec.streamChunkN == 1);
rec.chunkResult = LG_CLIPBOARD_RESULT_ACCEPTED;
x11CBRequestReady(211);
CHECK(rec.streamChunkN == 3);
CHECK(rec.data[0].size == 9);
CHECK(memcmp(rec.data[0].data, "abcdefghi", 9) == 0);
rec.endResult = LG_CLIPBOARD_RESULT_BLOCKED;
propNull(A_TEXT, 8, 0);
property(window);
CHECK(rec.streamEndN == 1);
CHECK(rec.dataN == 0);
rec.endResult = LG_CLIPBOARD_RESULT_ACCEPTED;
x11CBRequestReady(211);
CHECK(rec.streamEndN == 2);
CHECK(rec.dataN == 1);
CHECK(rec.abortN == 0);
finish();
}
static void testIncrReadyRace(void)
{
start();
const unsigned long targets[] = { A_TEXT };
discover(622, targets, ARRAY_LENGTH(targets));
const Window window = request(213, LG_CLIPBOARD_DATA_TEXT);
const unsigned long capacity = 4;
prop32(x11atoms.INCR, &capacity, 1);
selection(window, A_TEXT, x11atoms.SEL_DATA);
const uint8_t data[] = "race";
rec.chunkResult = LG_CLIPBOARD_RESULT_BLOCKED;
rec.readyDuringChunk = true;
prop8(A_TEXT, data, sizeof(data) - 1);
property(window);
CHECK(rec.streamChunkN == 2);
CHECK(rec.data[0].size == sizeof(data) - 1);
rec.endResult = LG_CLIPBOARD_RESULT_BLOCKED;
rec.readyDuringEnd = true;
propNull(A_TEXT, 8, 0);
property(window);
CHECK(rec.streamEndN == 2);
CHECK(rec.dataN == 1);
CHECK(rec.abortN == 0);
finish();
}
static void * requestReadyThread(void * opaque)
{
x11CBRequestReady(*(const LG_ClipboardRequest *)opaque);
return NULL;
}
static void testIncrCancelRace(void)
{
start();
const unsigned long targets[] = { A_TEXT };
discover(623, targets, ARRAY_LENGTH(targets));
const LG_ClipboardRequest id = 214;
const Window window = request(id, LG_CLIPBOARD_DATA_TEXT);
const unsigned long capacity = 6;
prop32(x11atoms.INCR, &capacity, 1);
selection(window, A_TEXT, x11atoms.SEL_DATA);
const uint8_t data[] = "cancel";
rec.chunkResult = LG_CLIPBOARD_RESULT_BLOCKED;
prop8(A_TEXT, data, sizeof(data) - 1);
property(window);
CHECK(rec.streamChunkN == 1);
rec.chunkResult = LG_CLIPBOARD_RESULT_ACCEPTED;
pthread_mutex_lock(&readRaceLock);
readRacePause = true;
pthread_mutex_unlock(&readRaceLock);
pthread_t readyThread;
CHECK(pthread_create(&readyThread, NULL, requestReadyThread,
(void *)&id) == 0);
pthread_mutex_lock(&readRaceLock);
while (!readRaceEntered)
pthread_cond_wait(&readRaceCond, &readRaceLock);
pthread_mutex_unlock(&readRaceLock);
/* The callback is outside x11cb.lock and still consuming its chunk. */
x11CBRequestCancel(id, LG_CLIPBOARD_CANCEL_REPLACED);
pthread_mutex_lock(&readRaceLock);
readRaceResume = true;
pthread_cond_signal(&readRaceCond);
pthread_mutex_unlock(&readRaceLock);
CHECK(pthread_join(readyThread, NULL) == 0);
CHECK(rec.streamChunkN == 2);
CHECK(rec.data[0].size == sizeof(data) - 1);
CHECK(memcmp(rec.data[0].data, data, sizeof(data) - 1) == 0);
CHECK(rec.abortN == 0);
finish();
}
static void testIncrLargeProperty(void)
{
start();
const unsigned long targets[] = { A_TEXT };
discover(621, targets, ARRAY_LENGTH(targets));
const Window window = request(212, LG_CLIPBOARD_DATA_TEXT);
const unsigned long capacity = 70U * 1024U;
prop32(x11atoms.INCR, &capacity, 1);
selection(window, A_TEXT, x11atoms.SEL_DATA);
const size_t size = 70U * 1024U;
uint8_t * data = malloc(size);
CHECK(data);
for (size_t i = 0; i < size; ++i)
data[i] = (uint8_t)i;
prop8(A_TEXT, data, size);
property(window);
propNull(A_TEXT, 8, 0);
property(window);
CHECK(rec.streamChunkN == 2);
CHECK(rec.streamMaxChunk == KVMFR_CLIPBOARD_REPRESENTATION_BYTES);
CHECK(rec.dataN == 1);
CHECK(rec.data[0].size == size);
CHECK(memcmp(rec.data[0].data, data, size) == 0);
free(data);
finish();
}
static void testMalformed(void)
{
start();
const unsigned long targets[] = { A_TEXT };
discover(63, targets, ARRAY_LENGTH(targets));
Window window = request(301, LG_CLIPBOARD_DATA_TEXT);
const unsigned long bad = 1;
prop32(A_TEXT, &bad, 1);
selection(window, A_TEXT, x11atoms.SEL_DATA);
CHECK(rec.abortN == 1);
CHECK(rec.abort[0] == 301);
window = request(302, LG_CLIPBOARD_DATA_TEXT);
const unsigned long capacity = 8;
prop32(x11atoms.INCR, &capacity, 1);
selection(window, A_TEXT, x11atoms.SEL_DATA);
prop32(A_TEXT, &bad, 1);
property(window);
CHECK(rec.abortN == 2);
CHECK(rec.abort[1] == 302);
window = request(303, LG_CLIPBOARD_DATA_TEXT);
propNull(A_TEXT, 8, 3);
selection(window, A_TEXT, x11atoms.SEL_DATA);
CHECK(rec.abortN == 3);
CHECK(rec.abort[2] == 303);
CHECK(rec.dataN == 0);
finish();
}
static void testReplace(void)
{
start();
const unsigned long targets[] = { A_TEXT };
discover(64, targets, ARRAY_LENGTH(targets));
const Window first = request(401, LG_CLIPBOARD_DATA_TEXT);
const unsigned int convertN = rec.convertN;
owner(65);
CHECK(rec.convertN == convertN + 1);
CHECK(rec.abortN == 1);
CHECK(rec.abort[0] == 401);
CHECK(rec.window[1].window == first);
CHECK(rec.window[1].dead);
const Window second = request(402, LG_CLIPBOARD_DATA_TEXT);
owner(None);
CHECK(rec.abortN == 2);
CHECK(rec.abort[1] == 402);
CHECK(rec.releaseN == 1);
bool dead = false;
for (unsigned int i = 0; i < rec.windowN; ++i)
if (rec.window[i].window == second)
dead = rec.window[i].dead;
CHECK(dead);
x11CBRequest(403, LG_CLIPBOARD_DATA_TEXT);
CHECK(rec.abortN == 3);
CHECK(rec.abort[2] == 403);
finish();
}
static void testSource(void)
{
start();
x11CBNotice(LG_CLIPBOARD_DATA_PNG);
CHECK(rec.owner == x11.window);
selectionRequest(x11atoms.TARGETS, 700);
CHECK(rec.changeN == 1);
CHECK(rec.change[0].window == 900);
CHECK(rec.change[0].property == 700);
CHECK(rec.change[0].type == XA_ATOM);
CHECK(rec.change[0].format == 32);
CHECK(rec.change[0].count == 2);
const Atom * values = (const Atom *)rec.change[0].data;
CHECK(values[0] == x11atoms.TARGETS);
CHECK(values[1] == A_PNG);
CHECK(rec.sendN == 1);
CHECK(rec.send[0].xselection.property == 700);
selectionRequest(A_PNG, 701);
CHECK(rec.requestN == 1);
CHECK(rec.requestType == LG_CLIPBOARD_DATA_PNG);
CHECK(rec.requestStream);
CHECK(rec.sendN == 1);
const uint8_t data[] = { 1, 2, 3, 4 };
CHECK(rec.requestStream->begin(rec.requestOpaque,
LG_CLIPBOARD_DATA_PNG, LG_CLIPBOARD_SIZE_UNKNOWN) ==
LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(rec.changeN == 2);
CHECK(rec.change[1].property == 701);
CHECK(rec.change[1].type == x11atoms.INCR);
CHECK(rec.change[1].format == 32);
CHECK(rec.change[1].count == 1);
CHECK(rec.sendN == 2);
CHECK(rec.send[1].xselection.property == 701);
CHECK(rec.requestStream->chunk(rec.requestOpaque, 0,
data, ARRAY_LENGTH(data)) == LG_CLIPBOARD_RESULT_BLOCKED);
propertyState(900, 701, PropertyDelete);
CHECK(rec.requestReadyN == 1);
CHECK(rec.requestStream->chunk(rec.requestOpaque, 0,
data, ARRAY_LENGTH(data)) == LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(rec.changeN == 3);
CHECK(rec.change[2].property == 701);
CHECK(rec.change[2].type == A_PNG);
CHECK(rec.change[2].format == 8);
CHECK(rec.change[2].count == 4);
CHECK(memcmp(rec.change[2].data, data, sizeof(data)) == 0);
CHECK(rec.requestStream->end(rec.requestOpaque, ARRAY_LENGTH(data)) ==
LG_CLIPBOARD_RESULT_BLOCKED);
propertyState(900, 701, PropertyDelete);
CHECK(rec.requestReadyN == 2);
CHECK(rec.requestStream->end(rec.requestOpaque, ARRAY_LENGTH(data)) ==
LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(rec.changeN == 4);
CHECK(rec.change[3].property == 701);
CHECK(rec.change[3].type == A_PNG);
CHECK(rec.change[3].format == 8);
CHECK(rec.change[3].count == 0);
rec.requestCancelSync = true;
selectionRequest(A_PNG, 704);
CHECK(rec.requestN == 2);
CHECK(rec.sendN == 3);
CHECK(rec.send[2].xselection.property == None);
rec.requestCancelSync = false;
selectionRequest(A_TEXT, 702);
CHECK(rec.requestN == 2);
CHECK(rec.sendN == 4);
CHECK(rec.send[3].xselection.property == None);
rec.requestOK = false;
selectionRequest(A_PNG, 703);
CHECK(rec.requestN == 3);
CHECK(rec.sendN == 5);
CHECK(rec.send[4].xselection.property == None);
x11CBRelease();
CHECK(rec.owner == None);
finish();
}
static void testSourceBeginDuringRequest(void)
{
start();
x11CBNotice(LG_CLIPBOARD_DATA_PNG);
rec.requestBeginSync = true;
selectionRequest(A_PNG, 705);
CHECK(rec.requestN == 1);
CHECK(rec.requestBeginResult == LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(rec.requestOpaque);
CHECK(rec.changeN == 1);
CHECK(rec.change[0].type == x11atoms.INCR);
CHECK(rec.sendN == 1);
propertyState(900, 705, PropertyDelete);
CHECK(rec.requestStream->end(rec.requestOpaque, 0) ==
LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(rec.changeN == 2);
CHECK(rec.change[1].count == 0);
finish();
}
static void testTeardown(void)
{
start();
const unsigned int convertN = rec.convertN;
owner(66);
CHECK(rec.convertN == convertN + 1);
const Window targets = rec.convert[convertN].requestor;
const Window read = request(501, LG_CLIPBOARD_DATA_TEXT);
x11CBNotice(LG_CLIPBOARD_DATA_TEXT);
CHECK(rec.abortN == 1);
CHECK(rec.abort[0] == 501);
CHECK(rec.owner == x11.window);
CHECK(rec.destroyN == 2);
bool targetsDead = false;
bool readDead = false;
for (unsigned int i = 0; i < rec.windowN; ++i)
{
if (rec.window[i].window == targets)
targetsDead = rec.window[i].dead;
if (rec.window[i].window == read)
readDead = rec.window[i].dead;
}
CHECK(targetsDead);
CHECK(readDead);
x11CBRelease();
CHECK(rec.owner == None);
finish();
}
struct Test
{
const char * name;
void (*run)(void);
};
static const struct Test tests[] =
{
{ "targets" , testTargets },
{ "file-import" , testFileImport },
{ "file-incr" , testFileImportIncr },
{ "file-mate-import" , testFileImportMate },
{ "file-source" , testFileSource },
{ "file-mate-source" , testFileSourceMate },
{ "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)
{
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;
}