From ba9f2b85b68d1f8b2461af79a1b5b96ccf0b77a8 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Wed, 5 Jan 2022 19:15:05 +1100 Subject: [PATCH] [host/client] kvmfr: update to include extra user data about the VM This change allows the host to provide information to the client about how the VM is configured, information such as the UUID, CPU configuration and capture method both for informational display in the client as well as debugging in the client's logs. The format of the records allows this to be extended later with new record types without needing to bump the KVMFR version. --- client/src/main.c | 2 +- common/include/common/KVMFR.h | 43 ++++- host/include/interface/capture.h | 1 + .../Windows/capture/NVFBC/src/nvfbc.c | 45 +++-- host/src/app.c | 155 ++++++++++++++++-- 5 files changed, 212 insertions(+), 34 deletions(-) diff --git a/client/src/main.c b/client/src/main.c index 31e7681b..2a0d1c98 100644 --- a/client/src/main.c +++ b/client/src/main.c @@ -1250,7 +1250,7 @@ restart: waitCount = 100; const bool magicMatches = memcmp(udata->magic, KVMFR_MAGIC, sizeof(udata->magic)) == 0; - if (udataSize != sizeof(KVMFR) || !magicMatches || udata->version != KVMFR_VERSION) + if (udataSize < sizeof(KVMFR) || !magicMatches || udata->version != KVMFR_VERSION) { reportBadVersion(); if (magicMatches) diff --git a/common/include/common/KVMFR.h b/common/include/common/KVMFR.h index 320ff445..102de143 100644 --- a/common/include/common/KVMFR.h +++ b/common/include/common/KVMFR.h @@ -28,7 +28,7 @@ #include "types.h" #define KVMFR_MAGIC "KVMFR---" -#define KVMFR_VERSION 14 +#define KVMFR_VERSION 15 #define KVMFR_MAX_DAMAGE_RECTS 64 @@ -67,9 +67,50 @@ typedef struct KVMFR uint32_t version; char hostver[32]; KVMFRFeatureFlags features; + //KVMFRRecords start here if there are any } KVMFR; +typedef struct KVMFRRecord +{ + uint8_t type; + uint32_t size; + uint8_t data[]; +} +KVMFRRecord; + +enum +{ + KVMFR_RECORD_VMINFO = 1, + KVMFR_RECORD_OSINFO +}; + +enum +{ + KVMFR_OS_LINUX, + KVMFR_OS_BSD, + KVMFR_OS_OSX, + KVMFR_OS_WINDOWS, + KVMFR_OS_OTHER +}; + +typedef struct KVMFRRecord_VMInfo +{ + uint8_t uuid [16]; // the guest's UUID + char capture[32]; // the capture device in use + uint8_t cpus; // number of CPUs + uint8_t cores; // number of CPU cores + char model[]; +} +KVMFRRecord_VMInfo; + +typedef struct KVMFRRecord_OSInfo +{ + uint8_t os; // KVMFR_OS_* + char name[]; // friendly name +} +KVMFRRecord_OSInfo; + typedef struct KVMFRCursor { int16_t x, y; // cursor x & y position diff --git a/host/include/interface/capture.h b/host/include/interface/capture.h index 7c7a229c..a2e9faa9 100644 --- a/host/include/interface/capture.h +++ b/host/include/interface/capture.h @@ -105,6 +105,7 @@ typedef struct CaptureInterface ); bool (*init )(); + bool (*start )(); void (*stop )(); bool (*deinit )(); void (*free )(); diff --git a/host/platform/Windows/capture/NVFBC/src/nvfbc.c b/host/platform/Windows/capture/NVFBC/src/nvfbc.c index becf041f..f285ac29 100644 --- a/host/platform/Windows/capture/NVFBC/src/nvfbc.c +++ b/host/platform/Windows/capture/NVFBC/src/nvfbc.c @@ -193,7 +193,7 @@ static bool nvfbc_init(void) free(buffer); } - + int adapterIndex = option_get_int("nvfbc", "adapterIndex"); // NOTE: Calling this on hardware that doesn't support NvFBC such as GeForce @@ -232,18 +232,6 @@ static bool nvfbc_init(void) if (this->seperateCursor) this->cursorEvent = lgWrapEvent(event); - if (!this->mouseHookCreated) - { - mouseHook_install(on_mouseMove); - this->mouseHookCreated = true; - } - - if (!this->forceCompositionCreated) - { - dwmForceComposition(); - this->forceCompositionCreated = true; - } - if (diffRes != (1 << this->diffShift)) DEBUG_WARN("DiffMap block size not supported: %dx%d", diffRes, diffRes); @@ -267,16 +255,38 @@ static bool nvfbc_init(void) } } - Sleep(100); + ++this->formatVer; + return true; +} - if (this->seperateCursor && !lgCreateThread("NvFBCPointer", pointerThread, NULL, &this->pointerThread)) +static bool nvfbc_start(void) +{ + if (!this->mouseHookCreated) + { + mouseHook_install(on_mouseMove); + this->mouseHookCreated = true; + } + + if (!this->forceCompositionCreated) + { + dwmForceComposition(); + this->forceCompositionCreated = true; + } + + if (!this->stop) + { + DEBUG_ERROR("BUG: start called when not stopped"); + return true; + } + + this->stop = false; + if (this->seperateCursor && + !lgCreateThread("NvFBCPointer", pointerThread, NULL, &this->pointerThread)) { DEBUG_ERROR("Failed to create the NvFBCPointer thread"); - nvfbc_deinit(); return false; } - ++this->formatVer; return true; } @@ -698,6 +708,7 @@ struct CaptureInterface Capture_NVFBC = .create = nvfbc_create, .init = nvfbc_init, + .start = nvfbc_start, .stop = nvfbc_stop, .deinit = nvfbc_deinit, .free = nvfbc_free, diff --git a/host/src/app.c b/host/src/app.c index 869abb48..81d7f311 100644 --- a/host/src/app.c +++ b/host/src/app.c @@ -353,7 +353,13 @@ static bool captureStart(void) { if (!app.iface->init()) { - DEBUG_ERROR("Initialize the capture device"); + DEBUG_ERROR("Failed to initialize the capture device"); + return false; + } + + if (app.iface->start && !app.iface->start()) + { + DEBUG_ERROR("Failed to start the capture device"); return false; } } @@ -517,19 +523,129 @@ static void lgmpShutdown() app.pointerShapeValid = false; } +typedef struct KVMFRUserData +{ + size_t size; + size_t used; + uint8_t * data; +} +KVMFRUserData; + +static void * allocUserData(KVMFRUserData * dst, size_t need, bool consume) +{ + size_t avail = dst->size - dst->used; + if (need > avail) + { + size_t newSize = dst->size; + do + { + newSize += 1024; + avail = newSize - dst->used; + } + while(need > avail); + + dst->data = realloc(dst->data, newSize); + if (!dst->data) + { + DEBUG_ERROR("failed to realloc"); + return NULL; + } + + dst->size = newSize; + } + + void * ret = dst->data + dst->used; + if (consume) + dst->used += need; + + return ret; +} + +static bool appendBuffer(KVMFRUserData * dst, KVMFRRecord * rec, + const void * src, size_t size) +{ + void * mem = allocUserData(dst, size, true); + if (!mem) + return false; + + memcpy(mem, src, size); + rec->size += size; + + return true; +} + +static bool newKVMFRData(KVMFRUserData * dst) +{ + KVMFRRecord * record; + memset(dst, 0, sizeof(*dst)); + + KVMFR * kvmfr = allocUserData(dst, sizeof(*kvmfr), true); + if (!kvmfr) + return false; + + memcpy(kvmfr->magic, KVMFR_MAGIC, + min(sizeof(kvmfr->magic), sizeof(KVMFR_MAGIC))); + kvmfr->version = KVMFR_VERSION; + kvmfr->features = os_hasSetCursorPos() ? KVMFR_FEATURE_SETCURSORPOS : 0; + strncpy(kvmfr->hostver, BUILD_VERSION, sizeof(kvmfr->hostver) - 1); + + { + if (!(record = allocUserData(dst, sizeof(*record), true))) + return false; + + KVMFRRecord_VMInfo * vmInfo = allocUserData(dst, sizeof(*vmInfo), true); + if (!vmInfo) + return false; + + record->type = KVMFR_RECORD_VMINFO; + record->size = sizeof(*vmInfo); + + strncpy(vmInfo->capture, app.iface->shortName, sizeof(vmInfo->capture) - 1); + + char * model = allocUserData(dst, 1024, false); + if (!model) + return false; + + int cpus, cores; + if (lgCPUInfo(model, 1024, &cpus, &cores)) + { + vmInfo->cpus = cpus; + vmInfo->cores = cores; + const int modelLen = strlen(model) + 1; + record->size += modelLen; + dst->used += modelLen; + } + } + + { + if (!(record = allocUserData(dst, sizeof(*record), true))) + return false; + + KVMFRRecord_OSInfo * osInfo = allocUserData(dst, sizeof(*osInfo), true); + if (!osInfo) + return false; + + record->type = KVMFR_RECORD_OSINFO; + record->size = sizeof(*osInfo); + + osInfo->os = KVMFR_OS_OTHER; + if (!appendBuffer(dst, record, "Unknown", 9)) + return false; + } + +DEBUG_INFO("done KVMFRData"); + return true; +} + static bool lgmpSetup(struct IVSHMEM * shmDev) { - KVMFR udata = - { - .magic = KVMFR_MAGIC, - .version = KVMFR_VERSION, - .features = os_hasSetCursorPos() ? KVMFR_FEATURE_SETCURSORPOS : 0 - }; - strncpy(udata.hostver, BUILD_VERSION, sizeof(udata.hostver)-1); + KVMFRUserData udata = { 0 }; + if (!newKVMFRData(&udata)) + return false; LGMP_STATUS status; if ((status = lgmpHostInit(shmDev->mem, shmDev->size, &app.lgmp, - sizeof(udata), (uint8_t *)&udata)) != LGMP_OK) + udata.used, udata.data)) != LGMP_OK) { DEBUG_ERROR("lgmpHostInit Failed: %s", lgmpStatusString(status)); goto fail_init; @@ -588,12 +704,14 @@ static bool lgmpSetup(struct IVSHMEM * shmDev) goto fail_lgmp; } + free(udata.data); return true; fail_lgmp: lgmpShutdown(); fail_init: + free(udata.data); return false; } @@ -675,12 +793,6 @@ int app_main(int argc, char * argv[]) app.frameValid = false; app.pointerShapeValid = false; - if (!lgmpSetup(&shmDev)) - { - exitcode = LG_HOST_EXIT_FATAL; - goto fail_ivshmem; - } - int throttleFps = option_get_int("app", "throttleFPS"); int throttleUs = throttleFps ? 1000000 / throttleFps : 0; uint64_t previousFrameTime = 0; @@ -724,8 +836,21 @@ int app_main(int argc, char * argv[]) app.iface = iface; + if (!lgmpSetup(&shmDev)) + { + exitcode = LG_HOST_EXIT_FATAL; + goto fail_ivshmem; + } + LG_LOCK_INIT(app.pointerLock); + if (app.iface->start && !app.iface->start()) + { + DEBUG_ERROR("Failed to start the capture interface"); + exitcode = LG_HOST_EXIT_FATAL; + goto fail_lgmp; + } + while(app.state != APP_STATE_SHUTDOWN) { if (app.state == APP_STATE_REINIT)