Files
LookingGlass/idd/LGIdd/transport/lgmp/CLGMPControl.cpp
Geoffrey McRae 348f92d8b6 [idd] audio: bridge broker packets over LGMP streams
Add optional playback and capture streams to the LGMP transport.
Negotiate ownership over the existing control path and keep audio
availability isolated from display transport readiness.

Route broker packets through public LGMP stream APIs without changing
the LGMP queue table or shared-memory mapping.
2026-11-06 14:32:04 +11:00

354 lines
9.7 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 "transport/lgmp/CLGMPControl.h"
#include "CDebug.h"
#include "WCWrite.h"
#include <string.h>
#include <utility>
static const uint32_t MAX_POINTER_SIZE =
(uint32_t)(sizeof(KVMFRCursor) + (512 * 512 * 4));
static const uint32_t POINTER_POSITION_SIZE =
(uint32_t)sizeof(KVMFRCursor);
static const size_t WC_WRITE_MIN_SIZE = 4 * 1024;
static const struct LGMPQueueConfig POINTER_QUEUE_CONFIG =
{
LGMP_Q_POINTER, //queueID
LGMP_Q_POINTER_LEN, //numMesages
1000 //subTimeout
};
CLGMPControl::~CLGMPControl()
{
DeInit();
}
bool CLGMPControl::Initialize()
{
if (m_pointerQueue)
return true;
LGMP_STATUS status;
if ((status = m_host.CreateQueue(
POINTER_QUEUE_CONFIG, &m_pointerQueue)) != LGMP_OK)
{
DEBUG_ERROR("lgmpHostQueueCreate Failed (Pointer): %s",
lgmpStatusString(status));
return false;
}
for (int i = 0; i < LGMP_Q_POINTER_LEN; ++i)
{
if ((status = m_host.Allocate(
POINTER_POSITION_SIZE, &m_pointerMemory[i])) != LGMP_OK)
{
DEBUG_ERROR("lgmpHostMemAlloc Failed (Pointer): %s",
lgmpStatusString(status));
return false;
}
memset(lgmpHostMemPtr(m_pointerMemory[i]), 0, POINTER_POSITION_SIZE);
}
for (int i = 0; i < POINTER_SHAPE_BUFFERS; ++i)
{
if ((status = m_host.Allocate(
MAX_POINTER_SIZE, &m_pointerShapeMemory[i])) != LGMP_OK)
{
DEBUG_ERROR("lgmpHostMemAlloc Failed (Pointer Shapes): %s",
lgmpStatusString(status));
return false;
}
memset(lgmpHostMemPtr(m_pointerShapeMemory[i]), 0, MAX_POINTER_SIZE);
}
for (int i = 0; i < COLOR_TRANSFORM_BUFFERS; ++i)
{
if ((status = m_host.Allocate(
sizeof(KVMFRCursor) + sizeof(KVMFRColorTransform),
&m_pointerTransformMemory[i])) != LGMP_OK)
{
DEBUG_ERROR("lgmpHostMemAlloc Failed (Pointer Transform): %s",
lgmpStatusString(status));
return false;
}
memset(lgmpHostMemPtr(m_pointerTransformMemory[i]), 0,
sizeof(KVMFRCursor) + sizeof(KVMFRColorTransform));
}
return true;
}
void CLGMPControl::DeInit()
{
SetControlEvents(nullptr, {});
for (int i = 0; i < LGMP_Q_POINTER_LEN; ++i)
lgmpHostMemFree(&m_pointerMemory[i]);
for (int i = 0; i < POINTER_SHAPE_BUFFERS; ++i)
lgmpHostMemFree(&m_pointerShapeMemory[i]);
for (int i = 0; i < COLOR_TRANSFORM_BUFFERS; ++i)
lgmpHostMemFree(&m_pointerTransformMemory[i]);
m_pointerQueue = nullptr;
m_pointerMemoryIndex = 0;
m_pointerShapeIndex = 0;
m_pointerTransformIndex = 0;
}
LGMP_STATUS CLGMPControl::ReadDataWithSource(void * data, size_t * size,
uint32_t * sourceClientID)
{
return lgmpHostReadDataWithSource(
m_pointerQueue, data, size, sourceClientID);
}
LGMP_STATUS CLGMPControl::AckData()
{
return lgmpHostAckData(m_pointerQueue);
}
LGMP_STATUS CLGMPControl::PostForClient(uint64_t udata,
PLGMPMemory memory, uint32_t clientID, unsigned& recipients)
{
recipients = 0;
if (!m_pointerQueue || !memory || !clientID)
return LGMP_ERR_INVALID_ARGUMENT;
return lgmpHostQueuePostForClients(m_pointerQueue, udata, memory,
&clientID, 1, &recipients);
}
bool CLGMPControl::PayloadPending(PLGMPMemory memory) const
{
return m_pointerQueue && memory &&
lgmpHostQueuePayloadPending(m_pointerQueue, memory);
}
LGMP_STATUS CLGMPControl::GetClientIDs(
uint32_t clientIDs[LGMP_MAX_CLIENTS], unsigned& count) const
{
count = 0;
if (!m_pointerQueue || !clientIDs)
return LGMP_ERR_INVALID_ARGUMENT;
return lgmpHostGetClientIDs(m_pointerQueue, clientIDs, &count);
}
bool CLGMPControl::HasNewSubscribers()
{
return lgmpHostQueueNewSubs(m_pointerQueue) != 0;
}
void CLGMPControl::SetControlEvents(
IControlEvents * events, const ControlToken& token)
{
CSRWExclusiveLock lock(m_eventLock);
m_events = events;
m_token = events ? token : ControlToken {};
}
PLGMPMemory CLGMPControl::FindAvailable(
PLGMPMemory * memory, int count, int& index) const
{
for (int offset = 0; offset < count; ++offset)
{
const int candidate = (index + offset) % count;
if (memory[candidate] &&
!lgmpHostQueuePayloadPending(m_pointerQueue, memory[candidate]))
{
index = candidate;
return memory[candidate];
}
}
return nullptr;
}
ControlResult CLGMPControl::SendCursor(
const IDARG_OUT_QUERY_HWCURSOR& info,
const BYTE * data, size_t size, UINT sdrWhiteLevel)
{
if (!m_pointerQueue)
return ControlResult::FAILED;
const bool hasShape = info.CursorShapeInfo.CursorType !=
IDDCX_CURSOR_SHAPE_TYPE_UNINITIALIZED;
if (hasShape)
{
if (info.CursorShapeInfo.CursorType != IDDCX_CURSOR_SHAPE_TYPE_ALPHA &&
info.CursorShapeInfo.CursorType !=
IDDCX_CURSOR_SHAPE_TYPE_MASKED_COLOR)
{
DEBUG_ERROR("Unsupported pointer shape type: %u",
static_cast<unsigned>(info.CursorShapeInfo.CursorType));
return ControlResult::FAILED;
}
if (info.CursorShapeInfo.Height &&
info.CursorShapeInfo.Pitch > SIZE_MAX / info.CursorShapeInfo.Height)
{
DEBUG_ERROR("Pointer shape size overflow");
return ControlResult::FAILED;
}
const size_t required = static_cast<size_t>(
info.CursorShapeInfo.Height) * info.CursorShapeInfo.Pitch;
if (required != size || (required && !data) ||
required > MAX_POINTER_SIZE - sizeof(KVMFRCursor))
{
DEBUG_ERROR("Invalid pointer shape payload: %zu bytes", size);
return ControlResult::FAILED;
}
}
PLGMPMemory mem;
int * index;
int count;
if (!hasShape)
{
index = &m_pointerMemoryIndex;
count = LGMP_Q_POINTER_LEN;
mem = FindAvailable(m_pointerMemory, count, *index);
}
else
{
index = &m_pointerShapeIndex;
count = POINTER_SHAPE_BUFFERS;
mem = FindAvailable(m_pointerShapeMemory, count, *index);
}
if (!mem)
return ControlResult::RETRY;
KVMFRCursor * cursor = (KVMFRCursor *)lgmpHostMemPtr(mem);
cursor->sdrWhiteLevel = sdrWhiteLevel ?
sdrWhiteLevel : KVMFR_SDR_WHITE_LEVEL_DEFAULT;
uint32_t flags = CURSOR_FLAG_VISIBLE_VALID;
if (info.IsCursorVisible)
{
cursor->x = (int16_t)info.X;
cursor->y = (int16_t)info.Y;
flags |= CURSOR_FLAG_POSITION | CURSOR_FLAG_VISIBLE;
}
if (hasShape)
{
if (size)
{
if (size < WC_WRITE_MIN_SIZE)
memcpy(cursor + 1, data, size);
else
WCWrite::Copy(cursor + 1, data, size);
}
cursor->hx = (int8_t )info.CursorShapeInfo.XHot;
cursor->hy = (int8_t )info.CursorShapeInfo.YHot;
cursor->width = (uint32_t)info.CursorShapeInfo.Width;
cursor->height = (uint32_t)info.CursorShapeInfo.Height;
cursor->pitch = (uint32_t)info.CursorShapeInfo.Pitch;
switch (info.CursorShapeInfo.CursorType)
{
case IDDCX_CURSOR_SHAPE_TYPE_ALPHA:
cursor->type = CURSOR_TYPE_COLOR;
break;
case IDDCX_CURSOR_SHAPE_TYPE_MASKED_COLOR:
cursor->type = CURSOR_TYPE_MASKED_COLOR;
break;
}
flags |= CURSOR_FLAG_SHAPE;
}
WCWrite::Flush();
const LGMP_STATUS status =
lgmpHostQueuePost(m_pointerQueue, flags, mem);
if (status == LGMP_OK)
{
*index = (*index + 1) % count;
return ControlResult::APPLIED;
}
if (status == LGMP_ERR_QUEUE_FULL)
return ControlResult::RETRY;
DEBUG_ERROR("lgmpHostQueuePost Failed (Pointer): %s",
lgmpStatusString(status));
return ControlResult::FAILED;
}
ControlResult CLGMPControl::SetColorTransform(
std::shared_ptr<const D12ColorTransform> transform)
{
return SendColorTransform(transform);
}
ControlResult CLGMPControl::SendColorTransform(
const std::shared_ptr<const D12ColorTransform>& transform)
{
if (!m_pointerQueue || !m_pointerTransformMemory[0])
return ControlResult::FAILED;
PLGMPMemory mem = FindAvailable(m_pointerTransformMemory,
COLOR_TRANSFORM_BUFFERS, m_pointerTransformIndex);
if (!mem)
return ControlResult::RETRY;
KVMFRCursor * cursor = (KVMFRCursor *)lgmpHostMemPtr(mem);
KVMFRColorTransform * output =
(KVMFRColorTransform *)(cursor + 1);
output->flags = 0;
if (transform)
{
if (transform->matrixEnabled)
output->flags |= KVMFR_COLOR_TRANSFORM_MATRIX;
if (transform->lutEnabled)
output->flags |= KVMFR_COLOR_TRANSFORM_LUT;
memcpy(output->matrix, transform->matrix, sizeof(output->matrix));
output->scalar = transform->scalar;
WCWrite::Copy(output->lut, transform->lut, sizeof(output->lut));
}
WCWrite::Flush();
const LGMP_STATUS status = lgmpHostQueuePost(m_pointerQueue,
CURSOR_FLAG_COLOR_TRANSFORM, mem);
if (status == LGMP_OK)
{
m_pointerTransformIndex =
(m_pointerTransformIndex + 1) % COLOR_TRANSFORM_BUFFERS;
return ControlResult::APPLIED;
}
if (status == LGMP_ERR_QUEUE_FULL)
return ControlResult::RETRY;
DEBUG_ERROR("lgmpHostQueuePost Failed (Pointer Transform): %s",
lgmpStatusString(status));
return ControlResult::FAILED;
}
void CLGMPControl::RequestReplay()
{
CSRWSharedLock lock(m_eventLock);
if (m_events)
m_events->OnControlReplay(m_token);
}