Compare commits

..

9 Commits
a6 ... a7

Author SHA1 Message Date
Geoffrey McRae
3784e9c06f [client] added support for new header format 2017-12-17 05:21:02 +11:00
Geoffrey McRae
24709ef186 [host] dxgi: set the pitch on new frames 2017-12-17 05:20:40 +11:00
Geoffrey McRae
db59e67ddb [host] update capture classes to return the pitch 2017-12-17 05:05:56 +11:00
Geoffrey McRae
1815387bbb [host] update to use the new header format 2017-12-17 05:03:47 +11:00
Geoffrey McRae
6595374a2c [common] cleanup header format and move resettable members 2017-12-17 05:03:16 +11:00
Geoffrey McRae
9d8f3a79b0 [host] detect switch user event and wait instead of terminating
Fixes #22
2017-12-17 04:50:04 +11:00
Geoffrey McRae
bf85358715 Revert "[host] simplify dll loading"
This reverts commit b89a8fee04.
2017-12-17 04:04:25 +11:00
Geoffrey McRae
b77a34b8b6 [host] DXGI: fix Secure Desktop wait logic
Fixes #20
2017-12-17 02:27:48 +11:00
Geoffrey McRae
287b983d27 [client] opengl: fixed broken mipmap logic
mipmapping is turned off for images that are scaled up a it degrades the
output quality.
2017-12-16 11:25:01 +11:00
9 changed files with 165 additions and 103 deletions

View File

@@ -155,7 +155,7 @@ int renderThread(void * unused)
struct KVMFRHeader header;
LG_RendererCursor cursorType = LG_CURSOR_COLOR;
struct KVMFRCursor cursor = {};
struct KVMFRFrame cursor = {};
size_t cursorDataSize = 0;
uint8_t * cursorData = NULL;
@@ -203,12 +203,14 @@ int renderThread(void * unused)
{
// sainty check of the frame format
if (
header.frame.type >= FRAME_TYPE_MAX ||
header.frame.width == 0 ||
header.frame.height == 0 ||
header.frame.stride == 0 ||
header.frame.dataPos == 0 ||
header.frame.dataPos > state.shmSize
header.detail.frame.type >= FRAME_TYPE_MAX ||
header.detail.frame.width == 0 ||
header.detail.frame.height == 0 ||
header.detail.frame.stride == 0 ||
header.detail.frame.pitch == 0 ||
header.detail.frame.dataPos == 0 ||
header.detail.frame.dataPos > state.shmSize ||
header.detail.frame.pitch < header.detail.frame.width
){
usleep(1000);
continue;
@@ -216,19 +218,18 @@ int renderThread(void * unused)
// setup the renderer format with the frame format details
LG_RendererFormat lgrFormat;
lgrFormat.width = header.frame.width;
lgrFormat.height = header.frame.height;
lgrFormat.stride = header.frame.stride;
lgrFormat.width = header.detail.frame.width;
lgrFormat.height = header.detail.frame.height;
lgrFormat.stride = header.detail.frame.stride;
lgrFormat.pitch = header.detail.frame.pitch;
switch(header.frame.type)
switch(header.detail.frame.type)
{
case FRAME_TYPE_ARGB:
lgrFormat.pitch = header.frame.stride * 4;
lgrFormat.bpp = 32;
break;
case FRAME_TYPE_RGB:
lgrFormat.pitch = header.frame.stride * 3;
lgrFormat.bpp = 24;
break;
@@ -243,7 +244,7 @@ int renderThread(void * unused)
// check the header's dataPos is sane
const size_t dataSize = lgrFormat.height * lgrFormat.pitch;
if (header.frame.dataPos + dataSize > state.shmSize)
if (header.detail.frame.dataPos + dataSize > state.shmSize)
{
DEBUG_ERROR("The guest sent an invalid dataPos");
break;
@@ -262,10 +263,10 @@ int renderThread(void * unused)
break;
}
state.srcSize.x = header.frame.width;
state.srcSize.y = header.frame.height;
state.srcSize.x = header.detail.frame.width;
state.srcSize.y = header.detail.frame.height;
if (params.autoResize)
SDL_SetWindowSize(state.window, header.frame.width, header.frame.height);
SDL_SetWindowSize(state.window, header.detail.frame.width, header.detail.frame.height);
state.started = true;
updatePositionInfo();
@@ -273,8 +274,14 @@ int renderThread(void * unused)
// if we have saved shape info, send it now
if (cursorData)
{
if (!state.lgr->on_mouse_shape(state.lgrData, cursorType, cursor.w,
cursor.h, cursor.pitch, cursorData))
if (!state.lgr->on_mouse_shape(
state.lgrData,
cursorType,
cursor.width,
cursor.height,
cursor.pitch,
cursorData
))
{
DEBUG_ERROR("Failed to update mouse shape");
break;
@@ -288,7 +295,8 @@ int renderThread(void * unused)
// if we have a cursor position, send it now
if (state.haveCursorPos)
{
state.lgr->on_mouse_event(
state.lgr->on_mouse_event
(
state.lgrData,
state.cursorVisible,
state.cursor.x,
@@ -297,7 +305,7 @@ int renderThread(void * unused)
}
}
const uint8_t * data = (const uint8_t *)state.shm + header.frame.dataPos;
const uint8_t * data = (const uint8_t *)state.shm + header.detail.frame.dataPos;
if (!state.lgr->on_frame_event(state.lgrData, data))
{
DEBUG_ERROR("Failed to render the frame");
@@ -308,20 +316,20 @@ int renderThread(void * unused)
// if we have cursor data
if (header.flags & KVMFR_HEADER_FLAG_CURSOR)
{
if (header.cursor.flags & KVMFR_CURSOR_FLAG_POS)
if (header.detail.cursor.flags & KVMFR_CURSOR_FLAG_POS)
{
state.cursor.x = header.cursor.x;
state.cursor.y = header.cursor.y;
state.cursorVisible = header.cursor.flags & KVMFR_CURSOR_FLAG_VISIBLE;
state.cursor.x = header.detail.cursor.x;
state.cursor.y = header.detail.cursor.y;
state.cursorVisible = header.detail.cursor.flags & KVMFR_CURSOR_FLAG_VISIBLE;
state.haveCursorPos = true;
}
if (header.cursor.flags & KVMFR_CURSOR_FLAG_SHAPE)
if (header.detail.cursor.flags & KVMFR_CURSOR_FLAG_SHAPE)
{
if (state.lgr)
{
bool bad = false;
switch(header.cursor.type)
switch(header.detail.cursor.type)
{
case CURSOR_TYPE_COLOR : cursorType = LG_CURSOR_COLOR ; break;
case CURSOR_TYPE_MONOCHROME : cursorType = LG_CURSOR_MONOCHROME ; break;
@@ -336,14 +344,14 @@ int renderThread(void * unused)
break;
// check the data position is sane
const uint64_t dataSize = header.cursor.h * header.cursor.pitch;
if (header.cursor.dataPos + dataSize > state.shmSize)
const uint64_t dataSize = header.detail.frame.height * header.detail.frame.pitch;
if (header.detail.frame.dataPos + dataSize > state.shmSize)
{
DEBUG_ERROR("The guest sent an invalid mouse dataPos");
break;
}
const uint8_t * data = (const uint8_t *)state.shm + header.cursor.dataPos;
const uint8_t * data = (const uint8_t *)state.shm + header.detail.frame.dataPos;
if (!state.started)
{
// save off the cursor data so we can tell the renderer when it's ready
@@ -353,13 +361,19 @@ int renderThread(void * unused)
cursorData = (uint8_t *)malloc(dataSize);
cursorDataSize = dataSize;
}
memcpy(&cursor, &header.cursor, sizeof(struct KVMFRCursor));
memcpy(&cursor, &header.detail.frame, sizeof(struct KVMFRFrame));
memcpy(cursorData, data, dataSize);
continue;
}
if (!state.lgr->on_mouse_shape(state.lgrData, cursorType, header.cursor.w,
header.cursor.h, header.cursor.pitch, data))
if (!state.lgr->on_mouse_shape(
state.lgrData,
cursorType,
header.detail.frame.width,
header.detail.frame.height,
header.detail.frame.pitch,
data)
)
{
DEBUG_ERROR("Failed to update mouse shape");
break;
@@ -805,8 +819,7 @@ int run()
DEBUG_ERROR("Failed to map memory");
break;
}
state.shmSize = ivshmem_get_map_size();
state.shm->hostID = ivshmem_get_id();
state.shmSize = ivshmem_get_map_size();
if (params.useSpice)
{

View File

@@ -69,7 +69,6 @@ struct LGR_OpenGL
int fpsList;
int mouseList;
LG_RendererRect destRect;
bool mipmap;
bool hasTextures;
GLuint textures[TEXTURE_COUNT];
@@ -365,7 +364,7 @@ void lgr_opengl_on_resize(void * opaque, const int width, const int height, cons
if (!this || !this->configured)
return;
this->window.x = width;
this->window.x = width;
this->window.y = height;
memcpy(&this->destRect, &destRect, sizeof(LG_RendererRect));
@@ -634,15 +633,17 @@ bool lgr_opengl_on_frame_event(void * opaque, const uint8_t * data)
(this->format.width > this->destRect.w) ||
(this->format.height > this->destRect.h));
if (this->mipmap != mipmap)
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
mipmap ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
this->mipmap = mipmap;
}
if (mipmap)
{
glGenerateMipmap(GL_TEXTURE_2D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
}
else
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
}
glBindTexture(GL_TEXTURE_2D, 0);

View File

@@ -21,7 +21,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <stdint.h>
#define KVMFR_HEADER_MAGIC "[[KVMFR]]"
#define KVMFR_HEADER_VERSION 3
#define KVMFR_HEADER_VERSION 4
typedef enum FrameType
{
@@ -49,9 +49,6 @@ typedef struct KVMFRCursor
uint8_t flags; // KVMFR_CURSOR_FLAGS
int16_t x, y; // cursor x & y position
CursorType type; // shape buffer data type
uint8_t w, h; // shape width and height
uint8_t pitch; // shape row length in bytes
uint64_t dataPos; // offset to the cursor data
}
KVMFRCursor;
@@ -60,7 +57,8 @@ typedef struct KVMFRFrame
FrameType type; // the frame data type
uint32_t width; // the width
uint32_t height; // the height
uint32_t stride; // the row stride
uint32_t stride; // the row stride (zero if cursor data)
uint32_t pitch; // the row pitch (stride in bytes)
uint64_t dataPos; // offset to the frame
}
KVMFRFrame;
@@ -70,16 +68,19 @@ KVMFRFrame;
#define KVMFR_HEADER_FLAG_RESTART 4 // restart signal from client
#define KVMFR_HEADER_FLAG_READY 8 // ready signal from client
typedef struct KVMFRDetail
{
KVMFRFrame frame; // the frame information
KVMFRCursor cursor; // the cursor information
}
KVMFRDetail;
typedef struct KVMFRHeader
{
char magic[sizeof(KVMFR_HEADER_MAGIC)];
uint32_t version; // version of this structure
uint16_t hostID; // the host ivshmem client id
uint16_t guestID; // the guest ivshmem client id
uint32_t updateCount; // updated each change
uint8_t flags; // KVMFR_HEADER_FLAGS
KVMFRFrame frame; // the frame information
KVMFRCursor cursor; // the cursor information
KVMFRDetail detail; // details
}
KVMFRHeader;
KVMFRHeader;

View File

@@ -260,6 +260,7 @@ GrabStatus DXGI::GrabFrame(FrameInfo & frame)
// send the last frame again if we timeout to prevent the client stalling on restart
frame.width = m_desc.Width;
frame.height = m_desc.Height;
frame.pitch = m_rect.Pitch;
frame.stride = m_rect.Pitch / 4;
unsigned int size = m_height * m_rect.Pitch;
@@ -346,32 +347,34 @@ GrabStatus DXGI::GrabFrame(FrameInfo & frame)
if (SUCCEEDED(status))
break;
HDESK desktop = NULL;
switch (status)
{
case DXGI_ERROR_ACCESS_LOST: // desktop switch, mode change or switch DWM on or off
return GRAB_STATUS_REINIT;
// desktop switch, mode change, switch DWM on or off or Secure Desktop
case DXGI_ERROR_ACCESS_LOST:
// see if we can open the desktop, if so request a reinit
// if not the secure desktop is active so just wait for it
// instead of aborting out
desktop = OpenInputDesktop(0, TRUE, GENERIC_READ);
if (desktop)
{
// open suceeded, not on the secure desktop, return to reinit
CloseDesktop(desktop);
return GRAB_STATUS_REINIT;
}
// fall through
case WAIT_ABANDONED: // this can happen also during desktop switches, not documented by MS though
{
// see if we can open the desktop, if not the secure desktop
// is active so just wait for it instead of aborting out
HDESK desktop = NULL;
while(!desktop)
// this can happen during desktop switches also, not documented by MS though
case WAIT_ABANDONED:
do
{
desktop = OpenInputDesktop(0, TRUE, GENERIC_READ);
if (desktop)
break;
Sleep(100);
}
} while (!desktop);
CloseDesktop(desktop);
if (!ReInitialize())
{
DEBUG_ERROR("Failed to ReInitialize after lost access to desktop");
return GRAB_STATUS_ERROR;
}
continue;
}
return GRAB_STATUS_REINIT;
default:
// unknown failure
@@ -436,6 +439,7 @@ GrabStatus DXGI::GrabFrame(FrameInfo & frame)
frame.width = m_desc.Width;
frame.height = m_desc.Height;
frame.pitch = m_rect.Pitch;
frame.stride = m_rect.Pitch / 4;
unsigned int size = m_height * m_rect.Pitch;

View File

@@ -61,10 +61,11 @@ bool NvFBC::Initialize(CaptureOptions * options)
if (_strcmpi(*it, "nowait") == 0) { m_optNoWait = true ; continue; }
}
m_hDLL = LoadLibraryA(NVFBC_LIBRARY_NAME);
std::string nvfbc = Util::GetSystemRoot() + "\\" + NVFBC_LIBRARY_NAME;
m_hDLL = LoadLibraryA(nvfbc.c_str());
if (!m_hDLL)
{
DEBUG_ERROR("Failed to load the NvFBC library: %d - %s", GetLastError(), NVFBC_LIBRARY_NAME);
DEBUG_ERROR("Failed to load the NvFBC library: %d - %s", GetLastError(), nvfbc.c_str());
return false;
}
@@ -75,7 +76,7 @@ bool NvFBC::Initialize(CaptureOptions * options)
if (!m_fnCreateEx || !m_fnSetGlobalFlags || !m_fnGetStatusEx || !m_fnEnable)
{
DEBUG_ERROR("Unable to locate required entry points in %s", NVFBC_LIBRARY_NAME);
DEBUG_ERROR("Unable to locate required entry points in %s", nvfbc.c_str());
DeInitialize();
return false;
}
@@ -288,6 +289,7 @@ enum GrabStatus NvFBC::GrabFrame(struct FrameInfo & frame)
}
frame.stride = frame.width;
frame.pitch = dataWidth;
uint8_t *src = (uint8_t *)m_frameBuffer + dataOffset;
uint8_t *dst = (uint8_t *)frame.buffer;
for(unsigned int y = 0; y < frame.height; ++y, dst += dataWidth, src += m_grabInfo.dwBufferWidth * 4)

View File

@@ -42,6 +42,7 @@ struct FrameInfo
unsigned int width;
unsigned int height;
unsigned int stride;
unsigned int pitch;
void * buffer;
size_t bufferSize;

View File

@@ -46,6 +46,7 @@ Service::Service() :
m_cursorData(NULL),
m_shapePending(false)
{
m_consoleSessionID = WTSGetActiveConsoleSessionId();
m_ivshmem = IVSHMEM::Get();
}
@@ -94,13 +95,11 @@ bool Service::Initialize(ICapture * captureDevice)
// update everything except for the hostID
memcpy(m_shmHeader->magic, KVMFR_HEADER_MAGIC, sizeof(KVMFR_HEADER_MAGIC));
m_shmHeader->version = KVMFR_HEADER_VERSION;
m_shmHeader->guestID = m_ivshmem->GetPeerID();
m_shmHeader->updateCount = 0;
// clear but retain the restart flag if it was set by the client
INTERLOCKED_AND8((char *)&m_shmHeader->flags, KVMFR_HEADER_FLAG_RESTART);
ZeroMemory(&m_shmHeader->frame , sizeof(KVMFRFrame ));
ZeroMemory(&m_shmHeader->cursor, sizeof(KVMFRCursor));
ZeroMemory(&m_shmHeader->detail, sizeof(KVMFRDetail));
m_initialized = true;
return true;
@@ -233,6 +232,13 @@ bool Service::Process()
case GRAB_STATUS_REINIT:
DEBUG_INFO("ReInitialize Requested");
if(WTSGetActiveConsoleSessionId() != m_consoleSessionID)
{
DEBUG_INFO("User switch detected, waiting to regain control");
while (WTSGetActiveConsoleSessionId() != m_consoleSessionID)
Sleep(100);
}
if (!m_capture->ReInitialize() || !InitPointers())
{
DEBUG_ERROR("ReInitialize Failed");
@@ -252,17 +258,18 @@ bool Service::Process()
}
uint8_t updateFlags = 0;
m_header.cursor.flags = 0;
m_detail.cursor.flags = 0;
if (!cursorOnly)
{
// signal a frame update
updateFlags |= KVMFR_HEADER_FLAG_FRAME;
m_header.frame.type = m_capture->GetFrameType();
m_header.frame.width = frame.width;
m_header.frame.height = frame.height;
m_header.frame.stride = frame.stride;
m_header.frame.dataPos = m_dataOffset[m_frameIndex];
m_detail.frame.type = m_capture->GetFrameType();
m_detail.frame.width = frame.width;
m_detail.frame.height = frame.height;
m_detail.frame.stride = frame.stride;
m_detail.frame.pitch = frame.pitch;
m_detail.frame.dataPos = m_dataOffset[m_frameIndex];
if (++m_frameIndex == 2)
m_frameIndex = 0;
}
@@ -280,11 +287,11 @@ bool Service::Process()
// tell the host where the cursor is
updateFlags |= KVMFR_HEADER_FLAG_CURSOR;
m_header.cursor.flags |= KVMFR_CURSOR_FLAG_POS;
m_detail.cursor.flags |= KVMFR_CURSOR_FLAG_POS;
if (m_cursor.visible)
m_header.cursor.flags |= KVMFR_CURSOR_FLAG_VISIBLE;
m_header.cursor.x = m_cursor.x;
m_header.cursor.y = m_cursor.y;
m_detail.cursor.flags |= KVMFR_CURSOR_FLAG_VISIBLE;
m_detail.cursor.x = m_cursor.x;
m_detail.cursor.y = m_cursor.y;
}
if (frame.cursor.hasShape || m_shapePending || (m_cursor.hasShape && restart))
@@ -323,15 +330,17 @@ bool Service::Process()
{
// give the host the new cursor shape
updateFlags |= KVMFR_HEADER_FLAG_CURSOR;
m_header.cursor.flags |= KVMFR_CURSOR_FLAG_SHAPE;
m_detail.cursor.flags |= KVMFR_CURSOR_FLAG_SHAPE;
if (m_cursor.visible)
m_header.cursor.flags |= KVMFR_CURSOR_FLAG_VISIBLE;
m_detail.cursor.flags |= KVMFR_CURSOR_FLAG_VISIBLE;
m_header.cursor.type = m_cursor.type;
m_header.cursor.w = m_cursor.w;
m_header.cursor.h = m_cursor.h;
m_header.cursor.pitch = m_cursor.pitch;
m_header.cursor.dataPos = m_dataOffset[m_frameIndex];
// shapes are sent as frame data
m_detail.cursor.type = m_cursor.type;
m_detail.frame.width = m_cursor.w;
m_detail.frame.height = m_cursor.h;
m_detail.frame.stride = 0;
m_detail.frame.pitch = m_cursor.pitch;
m_detail.frame.dataPos = m_dataOffset[m_frameIndex];
memcpy(m_frame[m_frameIndex], m_cursorData, m_cursor.dataSize);
m_shapePending = false;
@@ -344,13 +353,8 @@ bool Service::Process()
INTERLOCKED_AND8((volatile char *)flags, KVMFR_HEADER_FLAG_RESTART);
INTERLOCKED_OR8 ((volatile char *)flags, updateFlags);
// update the shared header but don't touch the setup fields
const size_t offset = (uintptr_t)&m_header.frame - (uintptr_t)&m_header;
memcpy(
(uint8_t *)m_shmHeader + offset,
(uint8_t *)&m_header + offset,
sizeof(KVMFRHeader) - offset
);
// update the shared details only
memcpy(&m_shmHeader->detail, &m_detail, sizeof(KVMFRDetail));
// increment the update count so the guest stops waiting
++m_shmHeader->updateCount;

View File

@@ -49,12 +49,13 @@ private:
~Service();
bool m_initialized;
DWORD m_consoleSessionID;
uint8_t * m_memory;
IVSHMEM * m_ivshmem;
HANDLE m_timer;
ICapture * m_capture;
KVMFRHeader m_header;
KVMFRDetail m_detail;
KVMFRHeader * m_shmHeader;
uint8_t * m_frame[2];
size_t m_frameSize;

View File

@@ -23,10 +23,45 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <inttypes.h>
#include <tmmintrin.h>
#include "common\debug.h"
class Util
{
public:
static std::string GetSystemRoot()
{
std::string defaultPath;
size_t pathSize;
char *libPath;
if (_dupenv_s(&libPath, &pathSize, "SystemRoot") != 0)
{
DEBUG_ERROR("Unable to get the SystemRoot environment variable");
return defaultPath;
}
if (!pathSize)
{
DEBUG_ERROR("The SystemRoot environment variable is not set");
return defaultPath;
}
#ifdef _WIN64
defaultPath = std::string(libPath) + "\\System32";
#else
if (IsWow64())
{
defaultPath = std::string(libPath) + "\\Syswow64";
}
else
{
defaultPath = std::string(libPath) + "\\System32";
}
#endif
return defaultPath;
}
static inline void BGRAtoRGB(uint8_t * orig, size_t imagesize, uint8_t * dest)
{
assert((uintptr_t)orig % 16 == 0);