[client] added support for LGMP

This commit is contained in:
Geoffrey McRae 2020-01-09 20:32:42 +11:00
parent 7a98a886b6
commit 2d755a45e0
5 changed files with 151 additions and 155 deletions

View File

@ -1 +1 @@
B1-57-g8caa220ad5+1 B1-61-g7a98a886b6+1

View File

@ -85,6 +85,8 @@ set(SOURCES
) )
add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common") add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common")
add_subdirectory("${PROJECT_TOP}/LGMP/lgmp" "${CMAKE_BINARY_DIR}/lgmp" )
add_subdirectory(spice) add_subdirectory(spice)
add_subdirectory(renderers) add_subdirectory(renderers)
add_subdirectory(clipboards) add_subdirectory(clipboards)
@ -96,6 +98,7 @@ target_compile_options(looking-glass-client PUBLIC ${PKGCONFIG_CFLAGS_OTHER})
target_link_libraries(looking-glass-client target_link_libraries(looking-glass-client
${EXE_FLAGS} ${EXE_FLAGS}
lg_common lg_common
lgmp
spice spice
renderers renderers
clipboards clipboards

View File

@ -236,41 +236,54 @@ static int renderThread(void * unused)
static int cursorThread(void * unused) static int cursorThread(void * unused)
{ {
KVMFRCursor header; LGMP_STATUS status;
PLGMPCQueue queue;
LG_RendererCursor cursorType = LG_CURSOR_COLOR; LG_RendererCursor cursorType = LG_CURSOR_COLOR;
uint32_t version = 0;
memset(&header, 0, sizeof(KVMFRCursor));
lgWaitEvent(e_startup, TIMEOUT_INFINITE); lgWaitEvent(e_startup, TIMEOUT_INFINITE);
// subscribe to the pointer queue
while(state.running) while(state.running)
{ {
// poll until we have cursor data status = lgmpClientSubscribe(state.lgmp, LGMP_Q_POINTER, &queue);
if(!(state.kvmfr->cursor.flags & KVMFR_CURSOR_FLAG_UPDATE) && if (status == LGMP_OK)
!(state.kvmfr->cursor.flags & KVMFR_CURSOR_FLAG_POS)) break;
if (status == LGMP_ERR_NO_SUCH_QUEUE)
{ {
if (!state.running) usleep(1000);
return 0;
usleep(params.cursorPollInterval);
continue; continue;
} }
// if the cursor was moved DEBUG_ERROR("lgmpClientSubscribe Failed: %s", lgmpStatusString(status));
bool moved = false; state.running = false;
if (state.kvmfr->cursor.flags & KVMFR_CURSOR_FLAG_POS) break;
}
while(state.running)
{
LGMPMessage msg;
if ((status = lgmpClientProcess(queue, &msg)) != LGMP_OK)
{ {
state.cursor.x = state.kvmfr->cursor.x; if (status == LGMP_ERR_QUEUE_EMPTY)
state.cursor.y = state.kvmfr->cursor.y; {
state.haveCursorPos = true; usleep(params.cursorPollInterval);
moved = true; continue;
}
DEBUG_ERROR("lgmpClientProcess Failed: %s", lgmpStatusString(status));
state.running = false;
break;
} }
// if this was only a move event KVMFRCursor * cursor = (KVMFRCursor *)msg.mem;
if (!(state.kvmfr->cursor.flags & KVMFR_CURSOR_FLAG_UPDATE)) state.cursor.x = cursor->x;
{ state.cursor.y = cursor->y;
// turn off the pos flag, trigger the event and continue state.haveCursorPos = true;
__sync_and_and_fetch(&state.kvmfr->cursor.flags, ~KVMFR_CURSOR_FLAG_POS);
// if this was only a move event
if (msg.udata == 0)
{
state.lgr->on_mouse_event state.lgr->on_mouse_event
( (
state.lgrData, state.lgrData,
@ -278,61 +291,40 @@ static int cursorThread(void * unused)
state.cursor.x, state.cursor.x,
state.cursor.y state.cursor.y
); );
lgmpClientMessageDone(queue);
continue; continue;
} }
// we must take a copy of the header to prevent the contained arguments switch(cursor->type)
// from being abused to overflow buffers.
memcpy(&header, &state.kvmfr->cursor, sizeof(struct KVMFRCursor));
if (header.flags & KVMFR_CURSOR_FLAG_SHAPE &&
header.version != version)
{ {
version = header.version; case CURSOR_TYPE_COLOR : cursorType = LG_CURSOR_COLOR ; break;
case CURSOR_TYPE_MONOCHROME : cursorType = LG_CURSOR_MONOCHROME ; break;
bool bad = false; case CURSOR_TYPE_MASKED_COLOR: cursorType = LG_CURSOR_MASKED_COLOR; break;
switch(header.type) default:
{ DEBUG_ERROR("Invalid cursor type");
case CURSOR_TYPE_COLOR : cursorType = LG_CURSOR_COLOR ; break; lgmpClientMessageDone(queue);
case CURSOR_TYPE_MONOCHROME : cursorType = LG_CURSOR_MONOCHROME ; break; continue;
case CURSOR_TYPE_MASKED_COLOR: cursorType = LG_CURSOR_MASKED_COLOR; break;
default:
DEBUG_ERROR("Invalid cursor type");
bad = true;
break;
}
if (bad)
break;
// check the data position is sane
const uint64_t dataSize = header.height * header.pitch;
if (header.dataPos + dataSize > state.shm.size)
{
DEBUG_ERROR("The guest sent an invalid mouse dataPos");
break;
}
const uint8_t * data = (const uint8_t *)state.kvmfr + header.dataPos;
if (!state.lgr->on_mouse_shape(
state.lgrData,
cursorType,
header.width,
header.height,
header.pitch,
data)
)
{
DEBUG_ERROR("Failed to update mouse shape");
break;
}
} }
// now we have taken the mouse data, we can flag to the host we are ready const uint8_t * data = (const uint8_t *)(cursor + 1);
state.kvmfr->cursor.flags = 0; if (!state.lgr->on_mouse_shape(
state.lgrData,
cursorType,
cursor->width,
cursor->height,
cursor->pitch,
data)
)
{
DEBUG_ERROR("Failed to update mouse shape");
lgmpClientMessageDone(queue);
continue;
}
bool showCursor = header.flags & KVMFR_CURSOR_FLAG_VISIBLE; bool showCursor = cursor->visible;
if (showCursor != state.cursorVisible || moved) lgmpClientMessageDone(queue);
if (showCursor != state.cursorVisible)
{ {
state.cursorVisible = showCursor; state.cursorVisible = showCursor;
state.lgr->on_mouse_event state.lgr->on_mouse_event
@ -345,67 +337,65 @@ static int cursorThread(void * unused)
} }
} }
lgmpClientUnsubscribe(&queue);
state.running = false;
return 0; return 0;
} }
static int frameThread(void * unused) static int frameThread(void * unused)
{ {
bool error = false; LGMP_STATUS status;
KVMFRFrame header; PLGMPCQueue queue;
memset(&header, 0, sizeof(struct KVMFRFrame));
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH); SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
lgWaitEvent(e_startup, TIMEOUT_INFINITE); lgWaitEvent(e_startup, TIMEOUT_INFINITE);
// subscribe to the frame queue
while(state.running) while(state.running)
{ {
// poll until we have a new frame status = lgmpClientSubscribe(state.lgmp, LGMP_Q_FRAME, &queue);
while(!(state.kvmfr->frame.flags & KVMFR_FRAME_FLAG_UPDATE)) if (status == LGMP_OK)
break;
if (status == LGMP_ERR_NO_SUCH_QUEUE)
{ {
if (!state.running)
break;
usleep(params.framePollInterval);
continue;
}
// we must take a copy of the header to prevent the contained
// arguments from being abused to overflow buffers.
memcpy(&header, &state.kvmfr->frame, sizeof(struct KVMFRFrame));
// tell the host to continue as the host buffers up to one frame
// we can be sure the data for this frame wont be touched
__sync_and_and_fetch(&state.kvmfr->frame.flags, ~KVMFR_FRAME_FLAG_UPDATE);
// sainty check of the frame format
if (
header.type >= FRAME_TYPE_MAX ||
header.width == 0 ||
header.height == 0 ||
header.pitch == 0 ||
header.dataPos == 0 ||
header.dataPos > state.shm.size ||
header.pitch < header.width
){
DEBUG_WARN("Bad header");
DEBUG_WARN(" width : %u" , header.width );
DEBUG_WARN(" height : %u" , header.height );
DEBUG_WARN(" pitch : %u" , header.pitch );
DEBUG_WARN(" dataPos: 0x%08lx", header.dataPos);
usleep(1000); usleep(1000);
continue; continue;
} }
DEBUG_ERROR("lgmpClientSubscribe Failed: %s", lgmpStatusString(status));
state.running = false;
break;
}
while(state.running)
{
LGMPMessage msg;
if ((status = lgmpClientProcess(queue, &msg)) != LGMP_OK)
{
if (status == LGMP_ERR_QUEUE_EMPTY)
{
usleep(params.framePollInterval);
continue;
}
DEBUG_ERROR("lgmpClientProcess Failed: %s", lgmpStatusString(status));
break;
}
KVMFRFrame * frame = (KVMFRFrame *)msg.mem;
// setup the renderer format with the frame format details // setup the renderer format with the frame format details
LG_RendererFormat lgrFormat; LG_RendererFormat lgrFormat;
lgrFormat.type = header.type; lgrFormat.type = frame->type;
lgrFormat.width = header.width; lgrFormat.width = frame->width;
lgrFormat.height = header.height; lgrFormat.height = frame->height;
lgrFormat.stride = header.stride; lgrFormat.stride = frame->stride;
lgrFormat.pitch = header.pitch; lgrFormat.pitch = frame->pitch;
size_t dataSize; size_t dataSize;
switch(header.type) bool error = false;
switch(frame->type)
{ {
case FRAME_TYPE_RGBA: case FRAME_TYPE_RGBA:
case FRAME_TYPE_BGRA: case FRAME_TYPE_BGRA:
@ -427,35 +417,32 @@ static int frameThread(void * unused)
} }
if (error) if (error)
break;
// check the header's dataPos is sane
if (header.dataPos + dataSize > state.shm.size)
{ {
DEBUG_ERROR("The guest sent an invalid dataPos"); lgmpClientMessageDone(queue);
break; break;
} }
if (header.width != state.srcSize.x || header.height != state.srcSize.y) if (frame->width != state.srcSize.x || frame->height != state.srcSize.y)
{ {
state.srcSize.x = header.width; state.srcSize.x = frame->width;
state.srcSize.y = header.height; state.srcSize.y = frame->height;
state.haveSrcSize = true; state.haveSrcSize = true;
if (params.autoResize) if (params.autoResize)
SDL_SetWindowSize(state.window, header.width, header.height); SDL_SetWindowSize(state.window, frame->width, frame->height);
updatePositionInfo(); updatePositionInfo();
} }
FrameBuffer frame = (FrameBuffer)((uint8_t *)state.kvmfr + header.dataPos);
if (!state.lgr->on_frame_event(state.lgrData, lgrFormat, frame)) FrameBuffer fb = (FrameBuffer)(frame + 1);
if (!state.lgr->on_frame_event(state.lgrData, lgrFormat, fb))
{ {
DEBUG_ERROR("renderer on frame event returned failure"); DEBUG_ERROR("renderer on frame event returned failure");
break; break;
} }
lgmpClientMessageDone(queue);
++state.frameCount; ++state.frameCount;
} }
lgmpClientUnsubscribe(&queue);
state.running = false; state.running = false;
return 0; return 0;
} }
@ -1122,8 +1109,6 @@ static int lg_run()
return -1; return -1;
} }
state.kvmfr = (struct KVMFRHeader*)state.shm.mem;
// try to connect to the spice server // try to connect to the spice server
if (params.useSpiceInput || params.useSpiceClipboard) if (params.useSpiceInput || params.useSpiceClipboard)
{ {
@ -1322,34 +1307,27 @@ static int lg_run()
SDL_SetHintWithPriority(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, "1", SDL_HINT_OVERRIDE); SDL_SetHintWithPriority(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, "1", SDL_HINT_OVERRIDE);
SDL_SetEventFilter(eventFilter, NULL); SDL_SetEventFilter(eventFilter, NULL);
// flag the host that we are starting up this is important so that LGMP_STATUS status;
// the host wakes up if it is waiting on an interrupt, the host will while(true)
// also send us the current mouse shape since we won't know it yet {
DEBUG_INFO("Waiting for host to signal it's ready..."); if ((status = lgmpClientInit(state.shm.mem, state.shm.size, &state.lgmp)) == LGMP_OK)
__sync_or_and_fetch(&state.kvmfr->flags, KVMFR_HEADER_FLAG_RESTART); break;
while(state.running && (state.kvmfr->flags & KVMFR_HEADER_FLAG_RESTART)) if (status == LGMP_ERR_INVALID_SESSION)
SDL_WaitEventTimeout(NULL, 1000); {
SDL_WaitEventTimeout(NULL, 1000);
continue;
}
DEBUG_ERROR("lgmpClientInit Failed: %s", lgmpStatusString(status));
return -1;
}
if (!state.running) if (!state.running)
return -1; return -1;
DEBUG_INFO("Host ready, starting session"); DEBUG_INFO("Host ready, starting session");
// check the header's magic and version are valid
if (memcmp(state.kvmfr->magic, KVMFR_HEADER_MAGIC, sizeof(KVMFR_HEADER_MAGIC)) != 0)
{
DEBUG_ERROR("Invalid header magic, is the host running?");
return -1;
}
if (state.kvmfr->version != KVMFR_HEADER_VERSION)
{
DEBUG_ERROR("KVMFR version missmatch, expected %u but got %u", KVMFR_HEADER_VERSION, state.kvmfr->version);
DEBUG_ERROR("This is not a bug, ensure you have the right version of looking-glass-host.exe on the guest");
return -1;
}
if (!lgCreateThread("cursorThread", cursorThread, NULL, &t_cursor)) if (!lgCreateThread("cursorThread", cursorThread, NULL, &t_cursor))
{ {
DEBUG_ERROR("cursor create thread failed"); DEBUG_ERROR("cursor create thread failed");
@ -1367,6 +1345,14 @@ static int lg_run()
{ {
SDL_WaitEventTimeout(NULL, 1000); SDL_WaitEventTimeout(NULL, 1000);
if (!lgmpClientSessionValid(state.lgmp))
{
DEBUG_WARN("Session is invalid, has the host shutdown?");
break;
}
(void)closeAlert;
/*
if (closeAlert == NULL) if (closeAlert == NULL)
{ {
if (state.kvmfr->flags & KVMFR_HEADER_FLAG_PAUSED) if (state.kvmfr->flags & KVMFR_HEADER_FLAG_PAUSED)
@ -1388,6 +1374,7 @@ static int lg_run()
closeAlert = NULL; closeAlert = NULL;
} }
} }
*/
} }
return 0; return 0;
@ -1400,6 +1387,8 @@ static void lg_shutdown()
if (t_render) if (t_render)
lgJoinThread(t_render, NULL); lgJoinThread(t_render, NULL);
lgmpClientFree(&state.lgmp);
if (e_startup) if (e_startup)
{ {
lgFreeEvent(e_startup); lgFreeEvent(e_startup);

View File

@ -26,6 +26,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "common/ivshmem.h" #include "common/ivshmem.h"
#include "spice/spice.h" #include "spice/spice.h"
#include <lgmp/client.h>
struct AppState struct AppState
{ {
@ -58,7 +59,9 @@ struct AppState
SDL_Window * window; SDL_Window * window;
struct IVSHMEM shm; struct IVSHMEM shm;
struct KVMFRHeader * kvmfr; PLGMPClient lgmp;
PLGMPCQueue frameQueue;
PLGMPCQueue pointerQueue;
uint64_t frameTime; uint64_t frameTime;
uint64_t lastFrameTime; uint64_t lastFrameTime;

View File

@ -25,12 +25,14 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <pthread.h> #include <pthread.h>
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
#include <stdatomic.h>
#include <stdint.h>
struct LGEvent struct LGEvent
{ {
pthread_mutex_t mutex; pthread_mutex_t mutex;
pthread_cond_t cond; pthread_cond_t cond;
bool flag; uint32_t flag;
bool autoReset; bool autoReset;
}; };
@ -58,7 +60,6 @@ LGEvent * lgCreateEvent(bool autoReset, unsigned int msSpinTime)
} }
handle->autoReset = autoReset; handle->autoReset = autoReset;
return handle; return handle;
} }
@ -81,7 +82,7 @@ bool lgWaitEvent(LGEvent * handle, unsigned int timeout)
return false; return false;
} }
while(!handle->flag) while(!atomic_load(&handle->flag))
{ {
if (timeout == TIMEOUT_INFINITE) if (timeout == TIMEOUT_INFINITE)
{ {
@ -109,7 +110,7 @@ bool lgWaitEvent(LGEvent * handle, unsigned int timeout)
} }
if (handle->autoReset) if (handle->autoReset)
handle->flag = false; atomic_store(&handle->flag, 0);
if (pthread_mutex_unlock(&handle->mutex) != 0) if (pthread_mutex_unlock(&handle->mutex) != 0)
{ {
@ -130,7 +131,7 @@ bool lgSignalEvent(LGEvent * handle)
return false; return false;
} }
handle->flag = true; atomic_store(&handle->flag, 1);
if (pthread_mutex_unlock(&handle->mutex) != 0) if (pthread_mutex_unlock(&handle->mutex) != 0)
{ {
@ -157,7 +158,7 @@ bool lgResetEvent(LGEvent * handle)
return false; return false;
} }
handle->flag = false; atomic_store(&handle->flag, 0);
if (pthread_mutex_unlock(&handle->mutex) != 0) if (pthread_mutex_unlock(&handle->mutex) != 0)
{ {