mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 15:11:31 +00:00
895 lines
22 KiB
C
895 lines
22 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 "spice.h"
|
|
#include "test.h"
|
|
|
|
#include "common/debug.h"
|
|
|
|
#include <purespice.h>
|
|
|
|
#include <pthread.h>
|
|
#include <stdatomic.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#define ARRAY_LENGTH(a) (sizeof(a) / sizeof((a)[0]))
|
|
#define WAIT_MS 2000U
|
|
|
|
extern const LG_TransportOps LGT_SPICE;
|
|
|
|
struct SpiceInput
|
|
{
|
|
bool available;
|
|
unsigned int onN;
|
|
unsigned int offN;
|
|
unsigned int onOrder;
|
|
unsigned int offFirst;
|
|
unsigned int offOrder;
|
|
};
|
|
|
|
struct SpiceClipboard
|
|
{
|
|
bool available;
|
|
unsigned int onN;
|
|
unsigned int offN;
|
|
unsigned int offFirst;
|
|
unsigned int offOrder;
|
|
};
|
|
|
|
struct Pure
|
|
{
|
|
PSConfig config;
|
|
atomic_bool connectOK;
|
|
atomic_bool callReady;
|
|
atomic_bool blockConnect;
|
|
atomic_bool cancel;
|
|
atomic_bool drop;
|
|
PSStatus dropStatus;
|
|
bool infoOK;
|
|
const char * serverName;
|
|
uint8_t uuid[16];
|
|
|
|
atomic_uint connectN;
|
|
atomic_uint disconnectN;
|
|
atomic_uint processN;
|
|
atomic_uint mouseModeN;
|
|
atomic_uint infoN;
|
|
atomic_uint freeInfoN;
|
|
atomic_uint order;
|
|
unsigned int targetSetOrder;
|
|
unsigned int connectOrder;
|
|
unsigned int disconnectOrder;
|
|
|
|
bool has[PS_CHANNEL_MAX];
|
|
bool connected[PS_CHANNEL_MAX];
|
|
bool connectResult[PS_CHANNEL_MAX];
|
|
bool disconnectResult[PS_CHANNEL_MAX];
|
|
unsigned int connectChannelN[PS_CHANNEL_MAX];
|
|
unsigned int disconnectChannelN[PS_CHANNEL_MAX];
|
|
};
|
|
|
|
struct SurfaceLog
|
|
{
|
|
unsigned int configN;
|
|
unsigned int destroyN;
|
|
unsigned int fillN;
|
|
unsigned int bitmapN;
|
|
unsigned int pointerN;
|
|
unsigned int width;
|
|
unsigned int height;
|
|
int x;
|
|
int y;
|
|
int w;
|
|
int h;
|
|
int stride;
|
|
uint32_t color;
|
|
bool topDown;
|
|
uint8_t bitmap[32];
|
|
size_t bitmapSize;
|
|
LG_TransportPointer pointer;
|
|
uint8_t shape[32];
|
|
size_t shapeSize;
|
|
atomic_bool block;
|
|
atomic_bool entered;
|
|
atomic_bool release;
|
|
};
|
|
|
|
struct Fixture
|
|
{
|
|
LG_Transport transport;
|
|
SpiceInput input;
|
|
SpiceClipboard clipboard;
|
|
struct SurfaceLog log;
|
|
};
|
|
|
|
static struct Pure ps;
|
|
static _Atomic(SpiceClipboard *) cbTarget;
|
|
|
|
static unsigned int nextOrder(void)
|
|
{
|
|
return atomic_fetch_add(&ps.order, 1) + 1;
|
|
}
|
|
|
|
void purespice_init(const PSInit * init)
|
|
{
|
|
(void)init;
|
|
}
|
|
|
|
bool purespice_connect(const PSConfig * config)
|
|
{
|
|
ps.config = *config;
|
|
ps.connectOrder = nextOrder();
|
|
atomic_fetch_add(&ps.connectN, 1);
|
|
|
|
while (atomic_load(&ps.blockConnect) && !atomic_load(&ps.cancel))
|
|
usleep(1000);
|
|
|
|
if (atomic_load(&ps.cancel) || !atomic_load(&ps.connectOK))
|
|
return false;
|
|
if (atomic_load(&ps.callReady) && config->ready)
|
|
config->ready();
|
|
return true;
|
|
}
|
|
|
|
void purespice_disconnect(void)
|
|
{
|
|
ps.disconnectOrder = nextOrder();
|
|
atomic_fetch_add(&ps.disconnectN, 1);
|
|
}
|
|
|
|
PSStatus purespice_process(int timeout)
|
|
{
|
|
(void)timeout;
|
|
atomic_fetch_add(&ps.processN, 1);
|
|
if (atomic_load(&ps.drop))
|
|
return ps.dropStatus;
|
|
usleep(1000);
|
|
return PS_STATUS_RUN;
|
|
}
|
|
|
|
bool purespice_getServerInfo(PSServerInfo * info)
|
|
{
|
|
atomic_fetch_add(&ps.infoN, 1);
|
|
if (!ps.infoOK)
|
|
return false;
|
|
|
|
memset(info, 0, sizeof(*info));
|
|
if (ps.serverName)
|
|
info->name = strdup(ps.serverName);
|
|
memcpy(info->uuid, ps.uuid, sizeof(info->uuid));
|
|
return true;
|
|
}
|
|
|
|
void purespice_freeServerInfo(PSServerInfo * info)
|
|
{
|
|
free(info->name);
|
|
info->name = NULL;
|
|
atomic_fetch_add(&ps.freeInfoN, 1);
|
|
}
|
|
|
|
bool purespice_hasChannel(PSChannelType channel)
|
|
{
|
|
return ps.has[channel];
|
|
}
|
|
|
|
bool purespice_channelConnected(PSChannelType channel)
|
|
{
|
|
return ps.connected[channel];
|
|
}
|
|
|
|
bool purespice_connectChannel(PSChannelType channel)
|
|
{
|
|
++ps.connectChannelN[channel];
|
|
if (!ps.connectResult[channel])
|
|
return false;
|
|
ps.connected[channel] = true;
|
|
return true;
|
|
}
|
|
|
|
bool purespice_disconnectChannel(PSChannelType channel)
|
|
{
|
|
++ps.disconnectChannelN[channel];
|
|
if (!ps.disconnectResult[channel])
|
|
return false;
|
|
ps.connected[channel] = false;
|
|
return true;
|
|
}
|
|
|
|
bool purespice_mouseMode(bool server)
|
|
{
|
|
(void)server;
|
|
atomic_fetch_add(&ps.mouseModeN, 1);
|
|
return true;
|
|
}
|
|
|
|
bool spiceInput_init(SpiceInput ** input)
|
|
{
|
|
*input = calloc(1, sizeof(**input));
|
|
return *input != NULL;
|
|
}
|
|
|
|
void spiceInput_free(SpiceInput ** input)
|
|
{
|
|
if (!input)
|
|
return;
|
|
free(*input);
|
|
*input = NULL;
|
|
}
|
|
|
|
const LG_InputOps * spiceInput_getOps(void)
|
|
{
|
|
static const LG_InputOps ops = { .name = "test" };
|
|
return &ops;
|
|
}
|
|
|
|
void spiceInput_setAvailable(SpiceInput * input, bool available)
|
|
{
|
|
input->available = available;
|
|
if (available)
|
|
{
|
|
++input->onN;
|
|
input->onOrder = nextOrder();
|
|
}
|
|
else
|
|
{
|
|
++input->offN;
|
|
input->offOrder = nextOrder();
|
|
if (input->offN == 1)
|
|
input->offFirst = input->offOrder;
|
|
}
|
|
}
|
|
|
|
bool spiceClipboard_init(SpiceClipboard ** clipboard)
|
|
{
|
|
*clipboard = calloc(1, sizeof(**clipboard));
|
|
return *clipboard != NULL;
|
|
}
|
|
|
|
void spiceClipboard_free(SpiceClipboard ** clipboard)
|
|
{
|
|
if (!clipboard)
|
|
return;
|
|
free(*clipboard);
|
|
*clipboard = NULL;
|
|
}
|
|
|
|
const LG_ClipboardOps * spiceClipboard_getOps(void)
|
|
{
|
|
static const LG_ClipboardOps ops = { .name = "test" };
|
|
return &ops;
|
|
}
|
|
|
|
void spiceClipboard_setAvailable(SpiceClipboard * clipboard, bool available)
|
|
{
|
|
clipboard->available = available;
|
|
if (available)
|
|
++clipboard->onN;
|
|
else
|
|
{
|
|
++clipboard->offN;
|
|
clipboard->offOrder = nextOrder();
|
|
if (clipboard->offN == 1)
|
|
clipboard->offFirst = clipboard->offOrder;
|
|
}
|
|
}
|
|
|
|
void spiceClipboard_setCallbackTarget(SpiceClipboard * clipboard)
|
|
{
|
|
atomic_store(&cbTarget, clipboard);
|
|
if (clipboard)
|
|
ps.targetSetOrder = nextOrder();
|
|
}
|
|
|
|
void spiceClipboard_notice(PSDataType type)
|
|
{
|
|
(void)type;
|
|
}
|
|
|
|
void spiceClipboard_data(PSDataType type, uint8_t * buffer, uint32_t size)
|
|
{
|
|
(void)type;
|
|
(void)buffer;
|
|
(void)size;
|
|
}
|
|
|
|
void spiceClipboard_release(void)
|
|
{
|
|
}
|
|
|
|
void spiceClipboard_request(PSDataType type)
|
|
{
|
|
(void)type;
|
|
}
|
|
|
|
void spiceClipboard_status(bool available)
|
|
{
|
|
SpiceClipboard * clipboard = atomic_load(&cbTarget);
|
|
if (clipboard)
|
|
spiceClipboard_setAvailable(clipboard, available);
|
|
}
|
|
|
|
static void resetPure(void)
|
|
{
|
|
memset(&ps, 0, sizeof(ps));
|
|
atomic_init(&ps.connectOK, true);
|
|
atomic_init(&ps.callReady, true);
|
|
atomic_init(&ps.blockConnect, false);
|
|
atomic_init(&ps.cancel, false);
|
|
atomic_init(&ps.drop, false);
|
|
atomic_init(&ps.connectN, 0);
|
|
atomic_init(&ps.disconnectN, 0);
|
|
atomic_init(&ps.processN, 0);
|
|
atomic_init(&ps.mouseModeN, 0);
|
|
atomic_init(&ps.infoN, 0);
|
|
atomic_init(&ps.freeInfoN, 0);
|
|
atomic_init(&ps.order, 0);
|
|
atomic_store(&cbTarget, NULL);
|
|
|
|
ps.dropStatus = PS_STATUS_ERR_READ;
|
|
ps.infoOK = true;
|
|
ps.serverName = "test-vm";
|
|
ps.uuid[0] = 0x42;
|
|
for (unsigned int i = 0; i < PS_CHANNEL_MAX; ++i)
|
|
{
|
|
ps.has[i] = true;
|
|
ps.connectResult[i] = true;
|
|
ps.disconnectResult[i] = true;
|
|
}
|
|
}
|
|
|
|
static void configure(void * opaque, unsigned int width,
|
|
unsigned int height)
|
|
{
|
|
struct SurfaceLog * log = opaque;
|
|
++log->configN;
|
|
log->width = width;
|
|
log->height = height;
|
|
if (atomic_load(&log->block))
|
|
{
|
|
atomic_store(&log->entered, true);
|
|
while (!atomic_load(&log->release))
|
|
usleep(1000);
|
|
}
|
|
}
|
|
|
|
static void destroy(void * opaque)
|
|
{
|
|
struct SurfaceLog * log = opaque;
|
|
++log->destroyN;
|
|
}
|
|
|
|
static void drawFill(void * opaque, int x, int y,
|
|
int width, int height, uint32_t color)
|
|
{
|
|
struct SurfaceLog * log = opaque;
|
|
++log->fillN;
|
|
log->x = x;
|
|
log->y = y;
|
|
log->w = width;
|
|
log->h = height;
|
|
log->color = color;
|
|
}
|
|
|
|
static void drawBitmap(void * opaque, bool topDown,
|
|
int x, int y, int width, int height, int stride, const void * data)
|
|
{
|
|
struct SurfaceLog * log = opaque;
|
|
++log->bitmapN;
|
|
log->topDown = topDown;
|
|
log->x = x;
|
|
log->y = y;
|
|
log->w = width;
|
|
log->h = height;
|
|
log->stride = stride;
|
|
log->bitmapSize = (size_t)height * (size_t)stride;
|
|
CHECK(log->bitmapSize <= sizeof(log->bitmap));
|
|
memcpy(log->bitmap, data, log->bitmapSize);
|
|
}
|
|
|
|
static void pointer(void * opaque, const LG_TransportPointer * pointer)
|
|
{
|
|
struct SurfaceLog * log = opaque;
|
|
++log->pointerN;
|
|
log->pointer = *pointer;
|
|
if (pointer->shape)
|
|
{
|
|
log->shapeSize = (size_t)pointer->height * pointer->pitch;
|
|
CHECK(log->shapeSize <= sizeof(log->shape));
|
|
memcpy(log->shape, pointer->shape, log->shapeSize);
|
|
log->pointer.shape = log->shape;
|
|
}
|
|
}
|
|
|
|
static const LG_SwSurfaceEventOps surfaceEvents =
|
|
{
|
|
.configure = configure,
|
|
.destroy = destroy,
|
|
.drawFill = drawFill,
|
|
.drawBitmap = drawBitmap,
|
|
.pointer = pointer,
|
|
};
|
|
|
|
static void initFixture(struct Fixture * f)
|
|
{
|
|
memset(f, 0, sizeof(*f));
|
|
f->transport.host = "test";
|
|
f->transport.port = 5900;
|
|
f->transport.inputEnabled = true;
|
|
f->transport.clipboardEnabled = true;
|
|
f->transport.input = &f->input;
|
|
f->transport.clipboard = &f->clipboard;
|
|
CHECK(spiceSurface_init(&f->transport.surface));
|
|
f->transport.connectEvent = lgCreateEvent(false, 0);
|
|
CHECK(f->transport.connectEvent);
|
|
atomic_init(&f->transport.stop, false);
|
|
atomic_init(&f->transport.sessionValid, false);
|
|
atomic_init(&f->log.block, false);
|
|
atomic_init(&f->log.entered, false);
|
|
atomic_init(&f->log.release, false);
|
|
}
|
|
|
|
static void freeFixture(struct Fixture * f)
|
|
{
|
|
LGT_SPICE.disconnect(&f->transport);
|
|
lgFreeEvent(f->transport.connectEvent);
|
|
f->transport.connectEvent = NULL;
|
|
spiceSurface_free(&f->transport.surface);
|
|
}
|
|
|
|
static void waitInvalid(LG_Transport * transport)
|
|
{
|
|
for (unsigned int i = 0; i < WAIT_MS; ++i)
|
|
{
|
|
if (!LGT_SPICE.sessionValid(transport))
|
|
return;
|
|
usleep(1000);
|
|
}
|
|
|
|
CHECK(!LGT_SPICE.sessionValid(transport));
|
|
}
|
|
|
|
static void waitBool(atomic_bool * value)
|
|
{
|
|
for (unsigned int i = 0; i < WAIT_MS; ++i)
|
|
{
|
|
if (atomic_load_explicit(value, memory_order_acquire))
|
|
return;
|
|
usleep(1000);
|
|
}
|
|
|
|
CHECK(atomic_load(value));
|
|
}
|
|
|
|
static bool cancel(void * opaque)
|
|
{
|
|
unsigned int * calls = opaque;
|
|
++*calls;
|
|
atomic_store(&ps.cancel, true);
|
|
return true;
|
|
}
|
|
|
|
static void testConnectFail(void)
|
|
{
|
|
resetPure();
|
|
atomic_store(&ps.connectOK, false);
|
|
struct Fixture f;
|
|
initFixture(&f);
|
|
|
|
LG_TransportSession session;
|
|
CHECK(LGT_SPICE.connect(&f.transport, &session) == LG_TRANSPORT_ERROR);
|
|
CHECK(!f.transport.thread);
|
|
CHECK(!LGT_SPICE.sessionValid(&f.transport));
|
|
CHECK(atomic_load(&ps.connectN) == 1);
|
|
CHECK(atomic_load(&ps.disconnectN) == 0);
|
|
CHECK(f.input.onN == 0);
|
|
CHECK(f.input.offN == 1);
|
|
CHECK(f.clipboard.offN == 1);
|
|
CHECK(!atomic_load(&cbTarget));
|
|
CHECK(ps.targetSetOrder < ps.connectOrder);
|
|
|
|
ps.config.ready();
|
|
CHECK(f.input.onN == 0);
|
|
CHECK(!LGT_SPICE.sessionValid(&f.transport));
|
|
freeFixture(&f);
|
|
}
|
|
|
|
static void testPreReadyDrop(void)
|
|
{
|
|
resetPure();
|
|
atomic_store(&ps.callReady, false);
|
|
atomic_store(&ps.drop, true);
|
|
ps.dropStatus = PS_STATUS_SHUTDOWN;
|
|
struct Fixture f;
|
|
initFixture(&f);
|
|
|
|
LG_TransportSession session;
|
|
CHECK(LGT_SPICE.connect(&f.transport, &session) == LG_TRANSPORT_ERROR);
|
|
CHECK(!f.transport.thread);
|
|
CHECK(!LGT_SPICE.sessionValid(&f.transport));
|
|
CHECK(atomic_load(&ps.connectN) == 1);
|
|
CHECK(atomic_load(&ps.processN) == 1);
|
|
CHECK(atomic_load(&ps.disconnectN) == 1);
|
|
CHECK(f.input.onN == 0);
|
|
CHECK(f.input.offN == 2);
|
|
CHECK(f.clipboard.offN == 2);
|
|
CHECK(f.input.offFirst < ps.disconnectOrder);
|
|
CHECK(f.clipboard.offFirst < ps.disconnectOrder);
|
|
CHECK(!atomic_load(&cbTarget));
|
|
freeFixture(&f);
|
|
}
|
|
|
|
static void testCancel(void)
|
|
{
|
|
resetPure();
|
|
atomic_store(&ps.blockConnect, true);
|
|
struct Fixture f;
|
|
initFixture(&f);
|
|
unsigned int cancelN = 0;
|
|
LG_TransportSession session;
|
|
|
|
CHECK(LGT_SPICE.connectCancellable(&f.transport, &session,
|
|
cancel, &cancelN) == LG_TRANSPORT_DISCONNECTED);
|
|
CHECK(cancelN == 1);
|
|
CHECK(!f.transport.thread);
|
|
CHECK(!LGT_SPICE.sessionValid(&f.transport));
|
|
CHECK(atomic_load(&ps.connectN) == 1);
|
|
CHECK(atomic_load(&ps.disconnectN) == 0);
|
|
CHECK(f.input.onN == 0);
|
|
CHECK(f.input.offN == 1);
|
|
CHECK(f.clipboard.offN == 1);
|
|
CHECK(!atomic_load(&cbTarget));
|
|
freeFixture(&f);
|
|
}
|
|
|
|
static void testReadyDrop(void)
|
|
{
|
|
resetPure();
|
|
struct Fixture f;
|
|
initFixture(&f);
|
|
const LG_VideoOps * video = LGT_SPICE.getVideoOps(&f.transport);
|
|
CHECK(video && video->type == LG_VIDEO_TYPE_SW_SURFACE);
|
|
CHECK(video->swSurface->attach(
|
|
&f.transport, &surfaceEvents, &f.log));
|
|
|
|
LG_TransportSession session;
|
|
CHECK(LGT_SPICE.connect(&f.transport, &session) == LG_TRANSPORT_OK);
|
|
CHECK(LGT_SPICE.sessionValid(&f.transport));
|
|
CHECK(strcmp(session.version, "SPICE") == 0);
|
|
CHECK(strcmp(session.name, "test-vm") == 0);
|
|
CHECK(session.uuidValid);
|
|
CHECK(session.uuid[0] == 0x42);
|
|
CHECK(session.os == LG_TRANSPORT_OS_OTHER);
|
|
CHECK(f.input.available);
|
|
CHECK(f.input.onN == 1);
|
|
CHECK(atomic_load(&ps.mouseModeN) == 1);
|
|
CHECK(atomic_load(&ps.infoN) == 1);
|
|
CHECK(atomic_load(&ps.freeInfoN) == 1);
|
|
CHECK(ps.targetSetOrder < ps.connectOrder);
|
|
CHECK(ps.connectOrder < f.input.onOrder);
|
|
|
|
ps.config.clipboard.status(true);
|
|
CHECK(f.clipboard.available);
|
|
ps.config.display.surfaceCreate(
|
|
0, PS_SURFACE_FMT_32_xRGB, 640, 480);
|
|
CHECK(f.log.configN == 1);
|
|
CHECK(video->swSurface->setActive(&f.transport, true));
|
|
|
|
atomic_store(&ps.drop, true);
|
|
waitInvalid(&f.transport);
|
|
LGT_SPICE.disconnect(&f.transport);
|
|
CHECK(!f.transport.thread);
|
|
CHECK(!f.input.available);
|
|
CHECK(!f.clipboard.available);
|
|
CHECK(f.input.offN >= 1);
|
|
CHECK(f.clipboard.offN >= 1);
|
|
CHECK(atomic_load(&ps.disconnectN) == 1);
|
|
CHECK(f.input.offFirst < ps.disconnectOrder);
|
|
CHECK(f.clipboard.offFirst < ps.disconnectOrder);
|
|
CHECK(!atomic_load(&cbTarget));
|
|
CHECK(LGT_SPICE.sendControl(&f.transport, NULL, NULL) ==
|
|
LG_TRANSPORT_DISCONNECTED);
|
|
|
|
const unsigned int inputOn = f.input.onN;
|
|
const unsigned int configN = f.log.configN;
|
|
ps.config.ready();
|
|
ps.config.display.surfaceCreate(
|
|
0, PS_SURFACE_FMT_32_xRGB, 800, 600);
|
|
CHECK(f.input.onN == inputOn);
|
|
CHECK(f.log.configN == configN);
|
|
video->swSurface->detach(&f.transport);
|
|
freeFixture(&f);
|
|
}
|
|
|
|
static void testSessionOrder(void)
|
|
{
|
|
resetPure();
|
|
struct Fixture first;
|
|
struct Fixture second;
|
|
initFixture(&first);
|
|
initFixture(&second);
|
|
|
|
LG_TransportSession session;
|
|
CHECK(LGT_SPICE.connect(&first.transport, &session) == LG_TRANSPORT_OK);
|
|
CHECK(LGT_SPICE.connect(&second.transport, &session) ==
|
|
LG_TRANSPORT_ERROR);
|
|
CHECK(!second.transport.thread);
|
|
CHECK(atomic_load(&ps.connectN) == 1);
|
|
CHECK(LGT_SPICE.sessionValid(&first.transport));
|
|
CHECK(!LGT_SPICE.sessionValid(&second.transport));
|
|
|
|
LGT_SPICE.disconnect(&first.transport);
|
|
CHECK(!LGT_SPICE.sessionValid(&first.transport));
|
|
CHECK(LGT_SPICE.connect(&second.transport, &session) == LG_TRANSPORT_OK);
|
|
CHECK(atomic_load(&ps.connectN) == 2);
|
|
CHECK(LGT_SPICE.sessionValid(&second.transport));
|
|
LGT_SPICE.disconnect(&second.transport);
|
|
freeFixture(&second);
|
|
freeFixture(&first);
|
|
}
|
|
|
|
static void testSurfaceActive(void)
|
|
{
|
|
resetPure();
|
|
struct Fixture f;
|
|
initFixture(&f);
|
|
const LG_SwSurfaceOps * sw =
|
|
spiceSurface_getVideoOps()->swSurface;
|
|
|
|
LG_SwSurfaceEventOps bad = surfaceEvents;
|
|
bad.pointer = NULL;
|
|
CHECK(!sw->attach(&f.transport, &bad, &f.log));
|
|
CHECK(sw->attach(&f.transport, &surfaceEvents, &f.log));
|
|
CHECK(!sw->setActive(&f.transport, true));
|
|
atomic_store(&f.transport.sessionValid, true);
|
|
|
|
ps.has[PS_CHANNEL_DISPLAY] = false;
|
|
CHECK(!sw->setActive(&f.transport, true));
|
|
ps.has[PS_CHANNEL_DISPLAY] = true;
|
|
ps.connectResult[PS_CHANNEL_DISPLAY] = false;
|
|
CHECK(!sw->setActive(&f.transport, true));
|
|
CHECK(ps.connectChannelN[PS_CHANNEL_DISPLAY] == 1);
|
|
CHECK(ps.connectChannelN[PS_CHANNEL_CURSOR] == 0);
|
|
|
|
ps.connectResult[PS_CHANNEL_DISPLAY] = true;
|
|
ps.connectResult[PS_CHANNEL_CURSOR] = false;
|
|
CHECK(!sw->setActive(&f.transport, true));
|
|
CHECK(ps.disconnectChannelN[PS_CHANNEL_DISPLAY] == 1);
|
|
CHECK(!ps.connected[PS_CHANNEL_DISPLAY]);
|
|
|
|
ps.connectResult[PS_CHANNEL_CURSOR] = true;
|
|
CHECK(sw->setActive(&f.transport, true));
|
|
const unsigned int displayConnect =
|
|
ps.connectChannelN[PS_CHANNEL_DISPLAY];
|
|
const unsigned int cursorConnect =
|
|
ps.connectChannelN[PS_CHANNEL_CURSOR];
|
|
CHECK(sw->setActive(&f.transport, true));
|
|
CHECK(ps.connectChannelN[PS_CHANNEL_DISPLAY] == displayConnect);
|
|
CHECK(ps.connectChannelN[PS_CHANNEL_CURSOR] == cursorConnect);
|
|
|
|
ps.disconnectResult[PS_CHANNEL_DISPLAY] = false;
|
|
CHECK(!sw->setActive(&f.transport, false));
|
|
CHECK(ps.disconnectChannelN[PS_CHANNEL_CURSOR] == 0);
|
|
ps.disconnectResult[PS_CHANNEL_DISPLAY] = true;
|
|
ps.disconnectResult[PS_CHANNEL_CURSOR] = false;
|
|
CHECK(!sw->setActive(&f.transport, false));
|
|
CHECK(ps.connected[PS_CHANNEL_DISPLAY]);
|
|
CHECK(ps.connectChannelN[PS_CHANNEL_DISPLAY] == displayConnect + 1);
|
|
|
|
ps.disconnectResult[PS_CHANNEL_CURSOR] = true;
|
|
CHECK(sw->setActive(&f.transport, false));
|
|
CHECK(!ps.connected[PS_CHANNEL_DISPLAY]);
|
|
CHECK(!ps.connected[PS_CHANNEL_CURSOR]);
|
|
sw->detach(&f.transport);
|
|
freeFixture(&f);
|
|
}
|
|
|
|
static void testSurfaceEvents(void)
|
|
{
|
|
resetPure();
|
|
struct Fixture f;
|
|
initFixture(&f);
|
|
const LG_SwSurfaceOps * sw =
|
|
spiceSurface_getVideoOps()->swSurface;
|
|
CHECK(sw->attach(&f.transport, &surfaceEvents, &f.log));
|
|
|
|
spiceSurface_create(f.transport.surface,
|
|
1, PS_SURFACE_FMT_32_xRGB, 20, 10);
|
|
CHECK(f.log.configN == 0);
|
|
spiceSurface_create(f.transport.surface,
|
|
0, PS_SURFACE_FMT_32_xRGB, 2, 1);
|
|
CHECK(f.log.configN == 1);
|
|
CHECK(f.log.width == 2);
|
|
CHECK(f.log.height == 1);
|
|
CHECK(f.log.fillN == 1);
|
|
|
|
spiceSurface_drawFill(
|
|
f.transport.surface, 0, 1, 2, 3, 4, 0xaabbccdd);
|
|
CHECK(f.log.fillN == 2);
|
|
CHECK(f.log.x == 1);
|
|
CHECK(f.log.y == 2);
|
|
CHECK(f.log.w == 3);
|
|
CHECK(f.log.h == 4);
|
|
CHECK(f.log.color == 0xaabbccdd);
|
|
|
|
uint8_t bitmap[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
|
|
spiceSurface_drawBitmap(f.transport.surface, 0,
|
|
PS_BITMAP_FMT_24BIT, true, 0, 0, 2, 1, 8, bitmap);
|
|
CHECK(f.log.bitmapN == 0);
|
|
spiceSurface_drawBitmap(f.transport.surface, 0,
|
|
PS_BITMAP_FMT_RGBA, true, 4, 5, 2, 1, 8, bitmap);
|
|
CHECK(f.log.bitmapN == 1);
|
|
CHECK(f.log.topDown);
|
|
CHECK(f.log.x == 4);
|
|
CHECK(f.log.y == 5);
|
|
CHECK(f.log.bitmapSize == sizeof(bitmap));
|
|
CHECK(memcmp(f.log.bitmap, bitmap, sizeof(bitmap)) == 0);
|
|
|
|
const uint8_t rgba[] =
|
|
{
|
|
1, 2, 3, 4,
|
|
5, 6, 7, 8,
|
|
};
|
|
const uint8_t bgra[] =
|
|
{
|
|
3, 2, 1, 4,
|
|
7, 6, 5, 8,
|
|
};
|
|
spiceSurface_setRGBAImage(
|
|
f.transport.surface, 2, 1, 6, 7, rgba);
|
|
CHECK(f.log.pointerN == 1);
|
|
CHECK(f.log.pointer.flags == LG_TRANSPORT_POINTER_SHAPE);
|
|
CHECK(f.log.pointer.type == CURSOR_TYPE_COLOR);
|
|
CHECK(f.log.pointer.hx == 6);
|
|
CHECK(f.log.pointer.hy == 7);
|
|
CHECK(f.log.shapeSize == sizeof(bgra));
|
|
CHECK(memcmp(f.log.shape, bgra, sizeof(bgra)) == 0);
|
|
|
|
spiceSurface_setPointerState(f.transport.surface, true, 8, 9);
|
|
CHECK(f.log.pointerN == 2);
|
|
CHECK(f.log.pointer.flags & LG_TRANSPORT_POINTER_VISIBLE);
|
|
CHECK(f.log.pointer.x == 8);
|
|
CHECK(f.log.pointer.y == 9);
|
|
CHECK(f.log.pointer.hx == 6);
|
|
CHECK(f.log.pointer.hy == 7);
|
|
|
|
spiceSurface_destroy(f.transport.surface, 0);
|
|
CHECK(f.log.destroyN == 1);
|
|
spiceSurface_drawFill(f.transport.surface, 0, 0, 0, 1, 1, 0);
|
|
CHECK(f.log.fillN == 2);
|
|
sw->detach(&f.transport);
|
|
freeFixture(&f);
|
|
}
|
|
|
|
struct CreateCall
|
|
{
|
|
SpiceSurface * surface;
|
|
};
|
|
|
|
static void * createSurface(void * opaque)
|
|
{
|
|
struct CreateCall * call = opaque;
|
|
spiceSurface_create(
|
|
call->surface, 0, PS_SURFACE_FMT_32_xRGB, 640, 480);
|
|
return NULL;
|
|
}
|
|
|
|
struct DetachCall
|
|
{
|
|
LG_Transport * transport;
|
|
const LG_SwSurfaceOps * ops;
|
|
atomic_bool started;
|
|
atomic_bool done;
|
|
};
|
|
|
|
static void * detachSurface(void * opaque)
|
|
{
|
|
struct DetachCall * call = opaque;
|
|
atomic_store(&call->started, true);
|
|
call->ops->detach(call->transport);
|
|
atomic_store(&call->done, true);
|
|
return NULL;
|
|
}
|
|
|
|
static void testSurfaceDetach(void)
|
|
{
|
|
resetPure();
|
|
struct Fixture f;
|
|
initFixture(&f);
|
|
const LG_SwSurfaceOps * sw =
|
|
spiceSurface_getVideoOps()->swSurface;
|
|
CHECK(sw->attach(&f.transport, &surfaceEvents, &f.log));
|
|
atomic_store(&f.log.block, true);
|
|
|
|
struct CreateCall create = { .surface = f.transport.surface };
|
|
pthread_t createThread;
|
|
CHECK(pthread_create(&createThread, NULL, createSurface, &create) == 0);
|
|
waitBool(&f.log.entered);
|
|
|
|
struct DetachCall detach =
|
|
{
|
|
.transport = &f.transport,
|
|
.ops = sw,
|
|
};
|
|
atomic_init(&detach.started, false);
|
|
atomic_init(&detach.done, false);
|
|
pthread_t detachThread;
|
|
CHECK(pthread_create(&detachThread, NULL, detachSurface, &detach) == 0);
|
|
waitBool(&detach.started);
|
|
usleep(10000);
|
|
CHECK(!atomic_load(&detach.done));
|
|
|
|
atomic_store(&f.log.release, true);
|
|
CHECK(pthread_join(createThread, NULL) == 0);
|
|
CHECK(pthread_join(detachThread, NULL) == 0);
|
|
CHECK(atomic_load(&detach.done));
|
|
CHECK(f.log.configN == 1);
|
|
CHECK(f.log.fillN == 1);
|
|
spiceSurface_drawFill(f.transport.surface, 0, 0, 0, 1, 1, 0);
|
|
CHECK(f.log.fillN == 1);
|
|
freeFixture(&f);
|
|
}
|
|
|
|
struct Test
|
|
{
|
|
const char * name;
|
|
void (*run)(void);
|
|
};
|
|
|
|
static const struct Test tests[] =
|
|
{
|
|
{ "connect-fail" , testConnectFail },
|
|
{ "pre-ready-drop", testPreReadyDrop },
|
|
{ "cancel" , testCancel },
|
|
{ "ready-drop" , testReadyDrop },
|
|
{ "session-order" , testSessionOrder },
|
|
{ "surface-active", testSurfaceActive },
|
|
{ "surface-events", testSurfaceEvents },
|
|
{ "surface-detach", testSurfaceDetach },
|
|
};
|
|
|
|
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;
|
|
}
|