mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-04 06:12:04 +00:00
Use calibrated copy-queue timestamps to separate source and effect waits from the actual framebuffer copy. Exclude producer readiness waits from client import timing so the same interval is not counted in both Copy and Import.
847 lines
24 KiB
C
847 lines
24 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 "interface/transport.h"
|
|
|
|
#include "common/KVMFR.h"
|
|
#include "common/LGMPConfig.h"
|
|
#include "common/debug.h"
|
|
#include "common/ivshmem.h"
|
|
#include "common/locking.h"
|
|
#include "common/option.h"
|
|
#include "common/stringutils.h"
|
|
|
|
#include <lgmp/client.h>
|
|
|
|
#include <dirent.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
struct DMAFrameInfo
|
|
{
|
|
const KVMFRFrame * frame;
|
|
size_t dataSize;
|
|
int fd;
|
|
};
|
|
|
|
struct LG_Transport
|
|
{
|
|
struct IVSHMEM shm;
|
|
PLGMPClient client;
|
|
PLGMPClientQueue frameQueue;
|
|
PLGMPClientQueue pointerQueue;
|
|
LG_Lock pointerLock;
|
|
|
|
unsigned cursorPollInterval;
|
|
unsigned framePollInterval;
|
|
bool allowDMA;
|
|
bool connected;
|
|
bool framePending;
|
|
const KVMFRFrame * pendingFrame;
|
|
uint32_t frameSerial;
|
|
bool formatValid;
|
|
LG_TransportFrameFormat format;
|
|
|
|
struct DMAFrameInfo dma[LGMP_Q_FRAME_LEN];
|
|
uint8_t * pointerData;
|
|
size_t pointerDataSize;
|
|
};
|
|
|
|
static bool lgmp_deviceValidator(struct Option * opt, const char ** error)
|
|
{
|
|
const char * transport = option_get_string("app", "transport");
|
|
if (!transport || strcmp(transport, "lgmp") != 0)
|
|
return true;
|
|
|
|
if (strlen(opt->value.x_string) > 3 &&
|
|
memcmp(opt->value.x_string, "kvmfr", 5) != 0)
|
|
{
|
|
struct stat st;
|
|
if (stat(opt->value.x_string, &st) != 0)
|
|
{
|
|
*error = "Invalid path to the shared memory file";
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static StringList lgmp_deviceValues(struct Option * option)
|
|
{
|
|
StringList values = stringlist_new(true);
|
|
DIR * dir = opendir("/sys/class/kvmfr");
|
|
if (!dir)
|
|
return values;
|
|
|
|
struct dirent * entry;
|
|
while ((entry = readdir(dir)))
|
|
{
|
|
if (entry->d_name[0] == '.')
|
|
continue;
|
|
char * name;
|
|
alloc_sprintf(&name, "/dev/%s", entry->d_name);
|
|
stringlist_push(values, name);
|
|
}
|
|
closedir(dir);
|
|
return values;
|
|
}
|
|
|
|
static void lgmp_setup(void)
|
|
{
|
|
struct stat st;
|
|
const char * defaultDevice = stat("/dev/kvmfr0", &st) == 0 ?
|
|
"/dev/kvmfr0" : "/dev/shm/looking-glass";
|
|
|
|
static struct Option options[] =
|
|
{
|
|
{
|
|
.module = "lgmp",
|
|
.name = "shmDevice",
|
|
.old_module = "app",
|
|
.old_name = "shmFile",
|
|
.shortopt = 'f',
|
|
.description = "Shared memory file or KVMFR device path",
|
|
.type = OPTION_TYPE_STRING,
|
|
.value.x_string = NULL,
|
|
.validator = lgmp_deviceValidator,
|
|
.getValues = lgmp_deviceValues,
|
|
},
|
|
{
|
|
.module = "lgmp",
|
|
.name = "allowDMA",
|
|
.old_module = "app",
|
|
.old_name = "allowDMA",
|
|
.description = "Allow direct DMA transfers when supported",
|
|
.type = OPTION_TYPE_BOOL,
|
|
.value.x_bool = true,
|
|
},
|
|
{
|
|
.module = "lgmp",
|
|
.name = "framePollInterval",
|
|
.old_module = "app",
|
|
.old_name = "framePollInterval",
|
|
.description = "Frame queue polling interval in microseconds",
|
|
.type = OPTION_TYPE_INT,
|
|
.value.x_int = 1000,
|
|
},
|
|
{
|
|
.module = "lgmp",
|
|
.name = "cursorPollInterval",
|
|
.old_module = "app",
|
|
.old_name = "cursorPollInterval",
|
|
.description = "Pointer queue polling interval in microseconds",
|
|
.type = OPTION_TYPE_INT,
|
|
.value.x_int = 1000,
|
|
},
|
|
{0}
|
|
};
|
|
|
|
options[0].value.x_string = (char *)defaultDevice;
|
|
option_register(options);
|
|
}
|
|
|
|
static bool lgmp_create(LG_Transport ** result)
|
|
{
|
|
struct LG_Transport * this = calloc(1, sizeof(*this));
|
|
if (!this)
|
|
return false;
|
|
|
|
this->allowDMA = option_get_bool("lgmp", "allowDMA");
|
|
const int framePoll = option_get_int("lgmp", "framePollInterval");
|
|
const int cursorPoll = option_get_int("lgmp", "cursorPollInterval");
|
|
if (framePoll < 0 || cursorPoll < 0)
|
|
{
|
|
DEBUG_ERROR("LGMP polling intervals cannot be negative");
|
|
free(this);
|
|
return false;
|
|
}
|
|
this->framePollInterval = framePoll;
|
|
this->cursorPollInterval = cursorPoll;
|
|
|
|
for (unsigned i = 0; i < LGMP_Q_FRAME_LEN; ++i)
|
|
this->dma[i].fd = -1;
|
|
|
|
LG_LOCK_INIT(this->pointerLock);
|
|
|
|
if (!ivshmemOpenDev(&this->shm,
|
|
option_get_string("lgmp", "shmDevice")))
|
|
{
|
|
LG_LOCK_FREE(this->pointerLock);
|
|
free(this);
|
|
return false;
|
|
}
|
|
|
|
LGMP_STATUS status = lgmpClientInit(this->shm.mem, this->shm.size,
|
|
&this->client);
|
|
if (status != LGMP_OK)
|
|
{
|
|
DEBUG_ERROR("lgmpClientInit failed: %s", lgmpStatusString(status));
|
|
ivshmemClose(&this->shm);
|
|
LG_LOCK_FREE(this->pointerLock);
|
|
free(this);
|
|
return false;
|
|
}
|
|
|
|
*result = this;
|
|
return true;
|
|
}
|
|
|
|
static void lgmp_stopFrame(struct LG_Transport * this)
|
|
{
|
|
if (this->framePending && this->frameQueue)
|
|
{
|
|
const LGMP_STATUS status = lgmpClientMessageDone(this->frameQueue);
|
|
if (status != LGMP_OK)
|
|
DEBUG_WARN("Failed to release pending LGMP frame: %s",
|
|
lgmpStatusString(status));
|
|
}
|
|
this->framePending = false;
|
|
this->pendingFrame = NULL;
|
|
LGMP_STATUS status = lgmpClientUnsubscribe(&this->frameQueue);
|
|
if (status != LGMP_OK)
|
|
{
|
|
DEBUG_WARN("Failed to unsubscribe from the LGMP frame queue: %s",
|
|
lgmpStatusString(status));
|
|
this->frameQueue = NULL;
|
|
}
|
|
|
|
this->frameSerial = 0;
|
|
this->formatValid = false;
|
|
}
|
|
|
|
static void lgmp_stopPointer(struct LG_Transport * this)
|
|
{
|
|
LG_LOCK(this->pointerLock);
|
|
const LGMP_STATUS status = lgmpClientUnsubscribe(&this->pointerQueue);
|
|
if (status != LGMP_OK)
|
|
{
|
|
DEBUG_WARN("Failed to unsubscribe from the LGMP pointer queue: %s",
|
|
lgmpStatusString(status));
|
|
this->pointerQueue = NULL;
|
|
}
|
|
LG_UNLOCK(this->pointerLock);
|
|
}
|
|
|
|
static void lgmp_closeQueues(struct LG_Transport * this)
|
|
{
|
|
lgmp_stopFrame(this);
|
|
lgmp_stopPointer(this);
|
|
}
|
|
|
|
static void lgmp_closeDMA(struct LG_Transport * this)
|
|
{
|
|
for (unsigned i = 0; i < LGMP_Q_FRAME_LEN; ++i)
|
|
{
|
|
if (this->dma[i].fd >= 0)
|
|
close(this->dma[i].fd);
|
|
this->dma[i].fd = -1;
|
|
this->dma[i].frame = NULL;
|
|
this->dma[i].dataSize = 0;
|
|
}
|
|
}
|
|
|
|
static void lgmp_destroy(LG_Transport ** transport)
|
|
{
|
|
if (!transport || !*transport)
|
|
return;
|
|
|
|
struct LG_Transport * this = *transport;
|
|
lgmp_closeQueues(this);
|
|
lgmp_closeDMA(this);
|
|
free(this->pointerData);
|
|
lgmpClientFree(&this->client);
|
|
ivshmemClose(&this->shm);
|
|
LG_LOCK_FREE(this->pointerLock);
|
|
free(this);
|
|
*transport = NULL;
|
|
}
|
|
|
|
static bool lgmp_parseSession(const uint8_t * data, uint32_t size,
|
|
LG_TransportSession * session)
|
|
{
|
|
if (size < sizeof(KVMFR))
|
|
return false;
|
|
|
|
const KVMFR * header = (const KVMFR *)data;
|
|
if (memcmp(header->magic, KVMFR_MAGIC, sizeof(header->magic)) != 0 ||
|
|
header->version != KVMFR_VERSION)
|
|
{
|
|
session->remoteVersion = header->version;
|
|
return false;
|
|
}
|
|
|
|
str_copy(session->version, sizeof(session->version), header->hostver,
|
|
sizeof(header->hostver));
|
|
if (header->features & KVMFR_FEATURE_SETCURSORPOS)
|
|
session->features |= LG_TRANSPORT_FEATURE_SET_CURSOR_POS;
|
|
if (header->features & KVMFR_FEATURE_WINDOWSIZE)
|
|
session->features |= LG_TRANSPORT_FEATURE_WINDOW_SIZE;
|
|
|
|
data += sizeof(*header);
|
|
size -= sizeof(*header);
|
|
while (size >= sizeof(KVMFRRecord))
|
|
{
|
|
const KVMFRRecord * record = (const KVMFRRecord *)data;
|
|
data += sizeof(*record);
|
|
size -= sizeof(*record);
|
|
if (record->size > size)
|
|
break;
|
|
|
|
switch (record->type)
|
|
{
|
|
case KVMFR_RECORD_VMINFO:
|
|
if (record->size >= sizeof(KVMFRRecord_VMInfo))
|
|
{
|
|
const KVMFRRecord_VMInfo * info =
|
|
(const KVMFRRecord_VMInfo *)data;
|
|
memcpy(session->uuid, info->uuid, sizeof(session->uuid));
|
|
for (unsigned i = 0; i < sizeof(session->uuid); ++i)
|
|
session->uuidValid |= session->uuid[i] != 0;
|
|
str_copy(session->capture, sizeof(session->capture), info->capture,
|
|
sizeof(info->capture));
|
|
session->cpus = info->cpus;
|
|
session->cores = info->cores;
|
|
session->sockets = info->sockets;
|
|
const size_t fixed = offsetof(KVMFRRecord_VMInfo, model);
|
|
str_copy(session->cpuModel, sizeof(session->cpuModel), info->model,
|
|
record->size - fixed);
|
|
}
|
|
break;
|
|
|
|
case KVMFR_RECORD_OSINFO:
|
|
if (record->size >= sizeof(KVMFRRecord_OSInfo))
|
|
{
|
|
const KVMFRRecord_OSInfo * info =
|
|
(const KVMFRRecord_OSInfo *)data;
|
|
session->os = info->os <= KVMFR_OS_OTHER ?
|
|
(LG_TransportGuestOS)info->os : LG_TRANSPORT_OS_OTHER;
|
|
const size_t fixed = offsetof(KVMFRRecord_OSInfo, name);
|
|
str_copy(session->osName, sizeof(session->osName), info->name,
|
|
record->size - fixed);
|
|
}
|
|
break;
|
|
}
|
|
|
|
data += record->size;
|
|
size -= record->size;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static LG_TransportStatus lgmp_connect(LG_Transport * this,
|
|
LG_TransportSession * session)
|
|
{
|
|
memset(session, 0, sizeof(*session));
|
|
session->os = LG_TRANSPORT_OS_OTHER;
|
|
|
|
uint32_t size;
|
|
uint8_t * data;
|
|
uint32_t remoteVersion;
|
|
LGMP_STATUS status = lgmpClientSessionInit(this->client, &size, &data, NULL,
|
|
&remoteVersion);
|
|
session->remoteVersion = remoteVersion;
|
|
switch (status)
|
|
{
|
|
case LGMP_OK:
|
|
if (!lgmp_parseSession(data, size, session))
|
|
return LG_TRANSPORT_INVALID_VERSION;
|
|
this->connected = true;
|
|
this->frameSerial = 0;
|
|
this->formatValid = false;
|
|
return LG_TRANSPORT_OK;
|
|
|
|
case LGMP_ERR_INVALID_VERSION:
|
|
return LG_TRANSPORT_INVALID_VERSION;
|
|
|
|
case LGMP_ERR_INVALID_SESSION:
|
|
case LGMP_ERR_INVALID_MAGIC:
|
|
return LG_TRANSPORT_UNAVAILABLE;
|
|
|
|
default:
|
|
DEBUG_ERROR("lgmpClientSessionInit failed: %s", lgmpStatusString(status));
|
|
return LG_TRANSPORT_ERROR;
|
|
}
|
|
}
|
|
|
|
static void lgmp_disconnect(LG_Transport * this)
|
|
{
|
|
lgmp_closeQueues(this);
|
|
lgmp_closeDMA(this);
|
|
this->connected = false;
|
|
}
|
|
|
|
static bool lgmp_sessionValid(LG_Transport * this)
|
|
{
|
|
return this->connected && lgmpClientSessionValid(this->client);
|
|
}
|
|
|
|
static bool lgmp_supportsDMA(LG_Transport * this)
|
|
{
|
|
return this->allowDMA && ivshmemHasDMA(&this->shm);
|
|
}
|
|
|
|
static bool lgmp_attachRenderer(LG_Transport * this,
|
|
const LG_RendererInterop * interop)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
static void lgmp_detachRenderer(LG_Transport * this)
|
|
{
|
|
}
|
|
|
|
static LG_TransportStatus lgmp_subscribe(PLGMPClient client, uint32_t id,
|
|
PLGMPClientQueue * queue)
|
|
{
|
|
if (*queue)
|
|
return LG_TRANSPORT_OK;
|
|
|
|
PLGMPClientQueue subscribed = NULL;
|
|
LGMP_STATUS status = lgmpClientSubscribe(client, id, &subscribed);
|
|
switch (status)
|
|
{
|
|
case LGMP_OK:
|
|
*queue = subscribed;
|
|
return LG_TRANSPORT_OK;
|
|
case LGMP_ERR_NO_SUCH_QUEUE: return LG_TRANSPORT_TIMEOUT;
|
|
case LGMP_ERR_INVALID_SESSION: return LG_TRANSPORT_DISCONNECTED;
|
|
default:
|
|
DEBUG_ERROR("lgmpClientSubscribe failed: %s", lgmpStatusString(status));
|
|
return LG_TRANSPORT_ERROR;
|
|
}
|
|
}
|
|
|
|
static LG_TransportStatus lgmp_process(PLGMPClientQueue queue,
|
|
unsigned interval, LGMPMessage * message)
|
|
{
|
|
LGMP_STATUS status = lgmpClientProcess(queue, message);
|
|
switch (status)
|
|
{
|
|
case LGMP_OK:
|
|
return LG_TRANSPORT_OK;
|
|
case LGMP_ERR_QUEUE_EMPTY:
|
|
if (interval)
|
|
usleep(interval);
|
|
return LG_TRANSPORT_TIMEOUT;
|
|
case LGMP_ERR_INVALID_SESSION:
|
|
return LG_TRANSPORT_DISCONNECTED;
|
|
default:
|
|
DEBUG_ERROR("lgmpClientProcess failed: %s", lgmpStatusString(status));
|
|
return LG_TRANSPORT_ERROR;
|
|
}
|
|
}
|
|
|
|
static int lgmp_getDMA(struct LG_Transport * this, const KVMFRFrame * frame,
|
|
size_t dataSize)
|
|
{
|
|
struct DMAFrameInfo * dma = NULL;
|
|
for (unsigned i = 0; i < LGMP_Q_FRAME_LEN; ++i)
|
|
if (this->dma[i].frame == frame)
|
|
{
|
|
dma = &this->dma[i];
|
|
if (dma->dataSize < dataSize && dma->fd >= 0)
|
|
{
|
|
close(dma->fd);
|
|
dma->fd = -1;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (!dma)
|
|
for (unsigned i = 0; i < LGMP_Q_FRAME_LEN; ++i)
|
|
if (!this->dma[i].frame)
|
|
{
|
|
dma = &this->dma[i];
|
|
dma->frame = frame;
|
|
break;
|
|
}
|
|
|
|
if (!dma)
|
|
return -1;
|
|
if (dma->fd >= 0)
|
|
return dma->fd;
|
|
|
|
const uintptr_t position = (uintptr_t)frame - (uintptr_t)this->shm.mem;
|
|
const uintptr_t offset = frame->offset + sizeof(FrameBuffer);
|
|
dma->dataSize = dataSize;
|
|
dma->fd = ivshmemGetDMABuf(&this->shm, position + offset, dataSize);
|
|
return dma->fd;
|
|
}
|
|
|
|
static LG_TransportStatus lgmp_nextFrame(LG_Transport * this, bool useDMA,
|
|
LG_TransportFrame * result)
|
|
{
|
|
if (!this->connected)
|
|
return LG_TRANSPORT_DISCONNECTED;
|
|
if (this->framePending)
|
|
return LG_TRANSPORT_ERROR;
|
|
|
|
LG_TransportStatus status = lgmp_subscribe(this->client, LGMP_Q_FRAME,
|
|
&this->frameQueue);
|
|
if (status != LG_TRANSPORT_OK)
|
|
{
|
|
if (status == LG_TRANSPORT_TIMEOUT)
|
|
usleep(1000);
|
|
return status;
|
|
}
|
|
|
|
LGMPMessage message;
|
|
status = lgmp_process(this->frameQueue, this->framePollInterval, &message);
|
|
if (status != LG_TRANSPORT_OK)
|
|
return status;
|
|
|
|
if (message.size < sizeof(KVMFRFrame))
|
|
{
|
|
lgmpClientMessageDone(this->frameQueue);
|
|
DEBUG_ERROR("LGMP frame payload is too small");
|
|
return LG_TRANSPORT_ERROR;
|
|
}
|
|
|
|
const KVMFRFrame * frame = (const KVMFRFrame *)message.mem;
|
|
const size_t frameDataSize = (size_t)frame->dataHeight * frame->pitch;
|
|
if (frame->type <= FRAME_TYPE_INVALID || frame->type >= FRAME_TYPE_MAX ||
|
|
frame->offset > message.size - sizeof(FrameBuffer) ||
|
|
frameDataSize > message.size - frame->offset - sizeof(FrameBuffer))
|
|
{
|
|
lgmpClientMessageDone(this->frameQueue);
|
|
DEBUG_ERROR("LGMP frame payload contains invalid dimensions or offsets");
|
|
return LG_TRANSPORT_ERROR;
|
|
}
|
|
|
|
if (frame->frameSerial == this->frameSerial && this->frameSerial)
|
|
{
|
|
lgmpClientMessageDone(this->frameQueue);
|
|
return LG_TRANSPORT_TIMEOUT;
|
|
}
|
|
this->frameSerial = frame->frameSerial;
|
|
|
|
memset(result, 0, sizeof(*result));
|
|
result->serial = frame->frameSerial;
|
|
if (frame->flags & FRAME_FLAG_BLOCK_SCREENSAVER)
|
|
result->flags |= LG_TRANSPORT_FRAME_BLOCK_SCREENSAVER;
|
|
if (frame->flags & FRAME_FLAG_REQUEST_ACTIVATION)
|
|
result->flags |= LG_TRANSPORT_FRAME_REQUEST_ACTIVATION;
|
|
if (frame->flags & FRAME_FLAG_TRUNCATED)
|
|
result->flags |= LG_TRANSPORT_FRAME_TRUNCATED;
|
|
|
|
LG_TransportFrameFormat * format = &this->format;
|
|
if (!this->formatValid || format->version != frame->formatVer)
|
|
{
|
|
memset(format, 0, sizeof(*format));
|
|
format->version = frame->formatVer;
|
|
format->type = frame->type;
|
|
format->screenWidth = frame->screenWidth;
|
|
format->screenHeight = frame->screenHeight;
|
|
format->dataWidth = frame->dataWidth;
|
|
format->dataHeight = frame->dataHeight;
|
|
format->frameWidth = frame->frameWidth;
|
|
format->frameHeight = frame->frameHeight;
|
|
format->rotation = frame->rotation;
|
|
format->stride = frame->stride;
|
|
format->pitch = frame->pitch;
|
|
format->hdr = frame->flags & FRAME_FLAG_HDR;
|
|
format->hdrPQ = frame->flags & FRAME_FLAG_HDR_PQ;
|
|
format->hdrMetadata = frame->flags & FRAME_FLAG_HDR_METADATA;
|
|
format->sdrWhiteLevel = frame->sdrWhiteLevel ? frame->sdrWhiteLevel :
|
|
KVMFR_SDR_WHITE_LEVEL_DEFAULT;
|
|
if (format->hdrMetadata)
|
|
{
|
|
memcpy(format->hdrDisplayPrimary, frame->hdrDisplayPrimary,
|
|
sizeof(format->hdrDisplayPrimary));
|
|
memcpy(format->hdrWhitePoint, frame->hdrWhitePoint,
|
|
sizeof(format->hdrWhitePoint));
|
|
format->hdrMaxDisplayLuminance = frame->hdrMaxDisplayLuminance;
|
|
format->hdrMinDisplayLuminance = frame->hdrMinDisplayLuminance;
|
|
format->hdrMaxContentLightLevel = frame->hdrMaxContentLightLevel;
|
|
format->hdrMaxFrameAverageLightLevel =
|
|
frame->hdrMaxFrameAverageLightLevel;
|
|
}
|
|
this->formatValid = true;
|
|
}
|
|
result->format = format;
|
|
|
|
result->framebuffer = (const FrameBuffer *)((const uint8_t *)frame +
|
|
frame->offset);
|
|
result->dmaFD = -1;
|
|
if (useDMA)
|
|
{
|
|
const size_t dataSize = (size_t)format->dataHeight * format->pitch;
|
|
result->dmaFD = lgmp_getDMA(this, frame, dataSize);
|
|
if (result->dmaFD < 0)
|
|
{
|
|
lgmpClientMessageDone(this->frameQueue);
|
|
DEBUG_ERROR("Failed to obtain a DMA buffer for the frame");
|
|
return LG_TRANSPORT_ERROR;
|
|
}
|
|
}
|
|
|
|
if (frame->damageRectsCount <= KVMFR_MAX_DAMAGE_RECTS)
|
|
{
|
|
result->damageRects = frame->damageRects;
|
|
result->damageRectsCount = frame->damageRectsCount;
|
|
}
|
|
else
|
|
DEBUG_WARN("Invalid damage rectangles, forcing a full update");
|
|
|
|
this->framePending = true;
|
|
this->pendingFrame = frame;
|
|
return LG_TRANSPORT_OK;
|
|
}
|
|
|
|
static bool lgmp_frameTimingReady(const KVMFRFrame * frame)
|
|
{
|
|
return __atomic_load_n(&frame->timingValid, __ATOMIC_ACQUIRE) &&
|
|
frame->timingSerial == frame->frameSerial;
|
|
}
|
|
|
|
static void lgmp_getFrameTiming(LG_Transport * this,
|
|
const LG_TransportFrame * frame, LG_TransportFrameTiming * timing)
|
|
{
|
|
memset(timing, 0, sizeof(*timing));
|
|
if (!this->framePending || !this->pendingFrame ||
|
|
this->pendingFrame->frameSerial != frame->serial)
|
|
return;
|
|
|
|
/* The producer writes these immediately after completing the framebuffer.
|
|
* nextFrame can observe the header earlier, so sample them only after the
|
|
* renderer's onFrame call has consumed the framebuffer. */
|
|
for (unsigned i = 0;
|
|
!lgmp_frameTimingReady(this->pendingFrame) &&
|
|
i < 1000;
|
|
++i)
|
|
usleep(1);
|
|
|
|
if (!lgmp_frameTimingReady(this->pendingFrame))
|
|
return;
|
|
|
|
timing->captureTime = this->pendingFrame->captureTime;
|
|
timing->postProcessTime = this->pendingFrame->postProcessTime;
|
|
timing->copyTime = this->pendingFrame->copyTime;
|
|
}
|
|
|
|
static void lgmp_releaseFrame(LG_Transport * this, LG_TransportFrame * frame)
|
|
{
|
|
if (this->framePending && this->frameQueue)
|
|
lgmpClientMessageDone(this->frameQueue);
|
|
this->framePending = false;
|
|
this->pendingFrame = NULL;
|
|
memset(frame, 0, sizeof(*frame));
|
|
}
|
|
|
|
static LG_TransportStatus lgmp_nextPointer(LG_Transport * this,
|
|
LG_TransportPointer * result)
|
|
{
|
|
if (!this->connected)
|
|
return LG_TRANSPORT_DISCONNECTED;
|
|
|
|
uint32_t pointerFlags = 0;
|
|
size_t shapeSize = 0;
|
|
size_t transformSize = 0;
|
|
LG_TransportStatus status = LG_TRANSPORT_OK;
|
|
if (!this->pointerQueue)
|
|
{
|
|
LG_LOCK(this->pointerLock);
|
|
status = lgmp_subscribe(this->client, LGMP_Q_POINTER,
|
|
&this->pointerQueue);
|
|
LG_UNLOCK(this->pointerLock);
|
|
if (status == LG_TRANSPORT_TIMEOUT)
|
|
usleep(1000);
|
|
}
|
|
if (status == LG_TRANSPORT_OK)
|
|
{
|
|
LGMPMessage message;
|
|
status = lgmp_process(this->pointerQueue, this->cursorPollInterval,
|
|
&message);
|
|
if (status == LG_TRANSPORT_OK)
|
|
{
|
|
if (message.size < sizeof(KVMFRCursor))
|
|
{
|
|
lgmpClientMessageDone(this->pointerQueue);
|
|
status = LG_TRANSPORT_ERROR;
|
|
}
|
|
else
|
|
{
|
|
const KVMFRCursor * cursor = (const KVMFRCursor *)message.mem;
|
|
shapeSize = message.udata & CURSOR_FLAG_SHAPE ?
|
|
(size_t)cursor->height * cursor->pitch : 0;
|
|
transformSize =
|
|
message.udata & CURSOR_FLAG_COLOR_TRANSFORM ?
|
|
sizeof(KVMFRColorTransform) : 0;
|
|
if (shapeSize > message.size - sizeof(*cursor) ||
|
|
transformSize > message.size - sizeof(*cursor) - shapeSize)
|
|
{
|
|
lgmpClientMessageDone(this->pointerQueue);
|
|
status = LG_TRANSPORT_ERROR;
|
|
}
|
|
else
|
|
{
|
|
const size_t needed = sizeof(*cursor) + shapeSize + transformSize;
|
|
if (needed > this->pointerDataSize)
|
|
{
|
|
void * data = realloc(this->pointerData, needed);
|
|
if (!data)
|
|
status = LG_TRANSPORT_ERROR;
|
|
else
|
|
{
|
|
this->pointerData = data;
|
|
this->pointerDataSize = needed;
|
|
}
|
|
}
|
|
if (status == LG_TRANSPORT_OK)
|
|
{
|
|
memcpy(this->pointerData, message.mem, needed);
|
|
pointerFlags = message.udata;
|
|
}
|
|
lgmpClientMessageDone(this->pointerQueue);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (status != LG_TRANSPORT_OK)
|
|
return status;
|
|
|
|
const KVMFRCursor * cursor = (const KVMFRCursor *)this->pointerData;
|
|
memset(result, 0, sizeof(*result));
|
|
if (pointerFlags & CURSOR_FLAG_POSITION)
|
|
result->flags |= LG_TRANSPORT_POINTER_POSITION;
|
|
if (pointerFlags & CURSOR_FLAG_VISIBLE)
|
|
result->flags |= LG_TRANSPORT_POINTER_VISIBLE;
|
|
if (pointerFlags & CURSOR_FLAG_SHAPE)
|
|
result->flags |= LG_TRANSPORT_POINTER_SHAPE;
|
|
if (pointerFlags & CURSOR_FLAG_COLOR_TRANSFORM)
|
|
result->flags |= LG_TRANSPORT_POINTER_COLOR_TRANSFORM;
|
|
if (pointerFlags & CURSOR_FLAG_VISIBLE_VALID)
|
|
result->flags |= LG_TRANSPORT_POINTER_VISIBLE_VALID;
|
|
result->x = cursor->x;
|
|
result->y = cursor->y;
|
|
result->type = cursor->type;
|
|
result->hx = cursor->hx;
|
|
result->hy = cursor->hy;
|
|
result->width = cursor->width;
|
|
result->height = cursor->height;
|
|
result->pitch = cursor->pitch;
|
|
result->sdrWhiteLevel = cursor->sdrWhiteLevel;
|
|
result->shape = (const uint8_t *)(cursor + 1);
|
|
if (transformSize)
|
|
result->colorTransform = (const LGColorTransform *)(result->shape +
|
|
shapeSize);
|
|
return LG_TRANSPORT_OK;
|
|
}
|
|
|
|
static void lgmp_releasePointer(LG_Transport * this,
|
|
LG_TransportPointer * pointer)
|
|
{
|
|
memset(pointer, 0, sizeof(*pointer));
|
|
}
|
|
|
|
static LG_TransportStatus lgmp_sendControl(LG_Transport * this,
|
|
const LG_TransportControl * control, LG_TransportControlToken * token)
|
|
{
|
|
uint8_t buffer[sizeof(KVMFRWindowSize)];
|
|
uint32_t size;
|
|
switch (control->type)
|
|
{
|
|
case LG_TRANSPORT_CONTROL_SET_CURSOR_POS:
|
|
{
|
|
const KVMFRSetCursorPos message = {
|
|
.msg.type = KVMFR_MESSAGE_SETCURSORPOS,
|
|
.x = control->cursorPos.x,
|
|
.y = control->cursorPos.y,
|
|
};
|
|
memcpy(buffer, &message, sizeof(message));
|
|
size = sizeof(message);
|
|
break;
|
|
}
|
|
case LG_TRANSPORT_CONTROL_WINDOW_SIZE:
|
|
{
|
|
const KVMFRWindowSize message = {
|
|
.msg.type = KVMFR_MESSAGE_WINDOWSIZE,
|
|
.w = control->windowSize.width,
|
|
.h = control->windowSize.height,
|
|
};
|
|
memcpy(buffer, &message, sizeof(message));
|
|
size = sizeof(message);
|
|
break;
|
|
}
|
|
default:
|
|
return LG_TRANSPORT_ERROR;
|
|
}
|
|
|
|
LG_LOCK(this->pointerLock);
|
|
if (!this->pointerQueue)
|
|
{
|
|
LG_UNLOCK(this->pointerLock);
|
|
return LG_TRANSPORT_UNAVAILABLE;
|
|
}
|
|
uint32_t serial;
|
|
LGMP_STATUS status = lgmpClientSendData(this->pointerQueue, buffer, size,
|
|
&serial);
|
|
LG_UNLOCK(this->pointerLock);
|
|
if (status != LGMP_OK)
|
|
return status == LGMP_ERR_INVALID_SESSION ? LG_TRANSPORT_DISCONNECTED :
|
|
LG_TRANSPORT_ERROR;
|
|
*token = serial;
|
|
return LG_TRANSPORT_OK;
|
|
}
|
|
|
|
static LG_TransportStatus lgmp_controlStatus(LG_Transport * this,
|
|
LG_TransportControlToken token)
|
|
{
|
|
LG_LOCK(this->pointerLock);
|
|
if (!this->pointerQueue)
|
|
{
|
|
LG_UNLOCK(this->pointerLock);
|
|
return LG_TRANSPORT_DISCONNECTED;
|
|
}
|
|
uint32_t serial;
|
|
LGMP_STATUS status = lgmpClientGetSerial(this->pointerQueue, &serial);
|
|
LG_UNLOCK(this->pointerLock);
|
|
if (status != LGMP_OK)
|
|
return status == LGMP_ERR_INVALID_SESSION ? LG_TRANSPORT_DISCONNECTED :
|
|
LG_TRANSPORT_ERROR;
|
|
return serial >= token ? LG_TRANSPORT_OK : LG_TRANSPORT_UNAVAILABLE;
|
|
}
|
|
|
|
const LG_TransportOps LGT_LGMP =
|
|
{
|
|
.name = "lgmp",
|
|
.setup = lgmp_setup,
|
|
.create = lgmp_create,
|
|
.destroy = lgmp_destroy,
|
|
.connect = lgmp_connect,
|
|
.disconnect = lgmp_disconnect,
|
|
.sessionValid = lgmp_sessionValid,
|
|
.supportsDMA = lgmp_supportsDMA,
|
|
.attachRenderer = lgmp_attachRenderer,
|
|
.detachRenderer = lgmp_detachRenderer,
|
|
.nextFrame = lgmp_nextFrame,
|
|
.getFrameTiming = lgmp_getFrameTiming,
|
|
.releaseFrame = lgmp_releaseFrame,
|
|
.stopFrame = lgmp_stopFrame,
|
|
.nextPointer = lgmp_nextPointer,
|
|
.releasePointer = lgmp_releasePointer,
|
|
.stopPointer = lgmp_stopPointer,
|
|
.sendControl = lgmp_sendControl,
|
|
.controlStatus = lgmp_controlStatus,
|
|
};
|