mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 15:11:31 +00:00
1187 lines
31 KiB
C
1187 lines
31 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 "../src/clipboard.h"
|
|
|
|
#include "common/debug.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
|
|
|
|
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;
|
|
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;
|
|
};
|
|
|
|
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, "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;
|
|
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);
|
|
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 == (64U * 1024U + 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 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++];
|
|
notice->count = count;
|
|
memcpy(notice->type, types, count * sizeof(*types));
|
|
}
|
|
|
|
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);
|
|
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 lgClipboard_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 lgClipboard_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 lgClipboard_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 lgClipboard_abort(LG_ClipboardRequest request)
|
|
{
|
|
CHECK(rec.abortN < ARRAY_LENGTH(rec.abort));
|
|
rec.abort[rec.abortN++] = request;
|
|
}
|
|
|
|
void lgClipboard_release(void)
|
|
{
|
|
++rec.releaseN;
|
|
}
|
|
|
|
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);
|
|
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;
|
|
}
|
|
|
|
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 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;
|
|
|
|
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 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 == 64U * 1024U);
|
|
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 },
|
|
{ "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;
|
|
}
|