Compare commits

..

21 Commits

Author SHA1 Message Date
Jonathan Rubenstein
25c88a1c6c [doc] faq: Correct misinformed question about mouse warp issue 2021-07-17 15:20:22 +10:00
Geoffrey McRae
7decb58bf7 [host] windows: fix build on Linux due to case sensitive filenames 2021-07-17 15:05:32 +10:00
Geoffrey McRae
d1ec19b30b [host] windows: fix delayExecution order of magnitude bug 2021-07-17 15:02:58 +10:00
Geoffrey McRae
74468cf799 [host] windows: remove accidental addition of some junk 2021-07-17 15:02:36 +10:00
Geoffrey McRae
411a6b1e49 [host] windows: add delayExecution function for more accurate sleeps
This change not only exposes and allows use of NtDelayExecution, but
also moves the code to set the system timer resolution.
2021-07-17 14:55:22 +10:00
Geoffrey McRae
e228165ff9 [host] windows: fix system timer resolution message units
ZwSetTimerResolution works in units of 100ns
2021-07-17 14:31:31 +10:00
Geoffrey McRae
d615514799 [host] windows: do not callback from the mouse hook context
The windows hook WH_MOUSE_LL is called in such a way that any delay in
processing causes a system wide stall. This change spawns an extra
thread which waits on an event set by the hook which is then used to
call the callback with an artifical limit of 1000Hz.
2021-07-17 14:03:52 +10:00
Jonathan Rubenstein
ed717351cf [doc] faq: Partial revert of fa871e9 to include registry hack
Expected to be fixed in B5
2021-07-16 00:47:22 +10:00
Jonathan Rubenstein
4658244686 [doc] build: Revise client build dependencies for B4 2021-07-16 00:47:22 +10:00
Quantum
48ae5c69f4 [client] wayland: fix typo in warp usage
The unwanted ! was introduced in 4b99bba200.
This basically caused warp to never be used.
2021-07-15 08:40:36 +10:00
Geoffrey McRae
4d065d577b [obs] call debug_init() to initialize debug print
Fixes a segfault reported in discord, DEBUG_* macros are not available
until `debug_init()` has been called as of commit
1effd5fddc
2021-07-13 07:42:25 +10:00
Geoffrey McRae
789ee70674 [host] dxgi: print out the adapter details earlier
Before we try and perhaps fail to init DXGI, we should print out what
the device is so that when there is an error report we can immediately
see if the user has the QXL device attached still.
2021-07-12 19:28:13 +10:00
Geoffrey McRae
3c0616bab7 [host] dxgi: print out the output device name to aid with support 2021-07-12 19:03:02 +10:00
Geoffrey McRae
3ce3b573a3 [host] app: fix infinate loop introduced in the last commit 2021-07-12 17:35:16 +10:00
Geoffrey McRae
ce459c24ce [host] app: wait for space in the frame queue
We must always wait for space in the frame queue so that we do not
overwrite memory that is already queued and may be in use by a client.
2021-07-12 17:30:06 +10:00
Geoffrey McRae
7d0b9711bd [host] nvfbc: remove the frameEvent event and associated code
Now that the host application can run the capture interface in
synchronous mode, and NVFBC uses this mode there is no longer need for
the frameEvent.
2021-07-12 17:01:23 +10:00
Geoffrey McRae
e477663a7e [host] app: allow the capture interface to select async or sync mode
While it's correct for DXGI to use a asyncronous waitFrame model, other
capture interfaces such as NvFBC it is not correct. This change allows
the capture interface to specify which is more correct for it and moves
the waitFrame/post into the main thread if async is not desired.
2021-07-12 16:57:22 +10:00
Quantum
eb01efe0cb [host] nvfbc: do not crash when protected content is playing
We return a timeout, so that when protected content finishes playing, we
can immediately resume capture.
2021-07-11 17:54:23 +10:00
Geoffrey McRae
8db4b65dee [host] app: allocate LGMP memory for KVMFRCursor updates without shapes
This changes the host to use a seperate pool of LGMP memory for cursor
positionl updates without shape information helping to prevent
corruption of the shape entries if they are still pending. While this is
not a perfect solution it resolves the issue without making major
changes to LGMP during the RC phase we are currently in.
2021-07-11 12:52:18 +10:00
Quantum
501b270890 [host] nvfbc: optimize change detection loop
Before, we only break out of the current row when a change is detected,
and all subsequent rows are still scanned. Now we break out of the entire
loop. This should make change detection ever so slightly faster.
2021-07-11 10:15:12 +10:00
Quantum
fd8f8b2b28 [host] dxgi: correctly mention AcquireNextFrame in help text
Also fix some formatting issues.

Co-Authored-By: Tudor Brindus <me@tbrindus.ca>
2021-07-11 10:15:12 +10:00
15 changed files with 406 additions and 213 deletions

View File

@@ -444,7 +444,7 @@ void waylandWarpPointer(int x, int y, bool exiting)
INTERLOCKED_SECTION(wlWm.confineLock,
{
if (!wlWm.lockedPointer)
if (wlWm.lockedPointer)
{
LG_UNLOCK(wlWm.confineLock);
return;

View File

@@ -59,14 +59,12 @@ Required Dependencies
- libgl-dev
- libfontconfig1-dev
- libgmp-dev
- libsdl2-dev
- libsdl2-ttf-dev
- libspice-protocol-dev
- make
- nettle-dev
- pkg-config
.. _may_be_disabled:
.. _client_deps_may_be_disabled:
May be disabled
<<<<<<<<<<<<<<<
@@ -83,6 +81,7 @@ feature is disabled when running :ref:`cmake <client_building>`.
- libx11-dev
- libxfixes-dev
- libxi-dev
- libxinerama-dev
- libxss-dev
- Disable with ``cmake -DENABLE_WAYLAND=no ..``
@@ -91,11 +90,33 @@ feature is disabled when running :ref:`cmake <client_building>`.
- libwayland-dev
- wayland-protocols
You can fetch these dependencies on Debian systems with the following command:
.. _client_deps_deprecated:
Deprecated
<<<<<<<<<<
These dependencies may be used, but are not required, and will be removed
in the future.
- Enable with ``cmake -DENABLE_SDL=yes ..``
- libsdl2-dev
- libsdl2-ttf-dev
.. _client_fetching_with_apt:
Fetching with APT
^^^^^^^^^^^^^^^^^
You can fetch these dependencies with the following command:
``apt-get install binutils-dev cmake fonts-freefont-ttf libfontconfig1-dev
libsdl2-dev libsdl2-ttf-dev libspice-protocol-dev libx11-dev nettle-dev
wayland-protocols``
libegl-dev libspice-protocol-dev nettle-dev libx11-dev libxi-dev libxinerama-dev
libxss-dev libwayland-dev wayland-protocols``
You may omit some dependencies, if you disable the feature which requires them
when running :ref:`cmake <client_building>`.
(See :ref:`client_deps_may_be_disabled`)
.. _client_building:

View File

@@ -88,13 +88,20 @@ The mouse is jumpy, slow, laggy when using SPICE
Please be sure to install the SPICE guest tools from
https://www.spice-space.org/download.html#windows-binaries.
.. _the_mouse_doesnt_stay_aligned_with_the_host.:
.. _mouse_desync_when_entering:
The mouse doesn't stay aligned with the host.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The mouse position is wrong when entering the window
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This is intentional. The host's mouse no longer interacts with your operating
system, and is completely captured by Looking Glass.
This is due to windows mouse acceleration, it can be disabled by
following one of these methods:
- Disabling pointer precision (Control Panel > Mouse > Pointer Options
> Uncheck Enhance pointer precision)
- By changing the acceleration behavior with the following registry
magic:
http://donewmouseaccel.blogspot.com.au/2010/03/markc-windows-7-mouse-acceleration-fix.html
(Contrary to the title this works just fine on Windows 10)
.. _the_cursor_position_doesnt_update_until_i_click:

View File

@@ -91,9 +91,10 @@ typedef void (*CapturePostPointerBuffer)(CapturePointer pointer);
typedef struct CaptureInterface
{
const char *shortName;
const char * (*getName )();
void (*initOptions )();
const char * shortName;
const bool asyncCapture;
const char * (*getName )();
void (*initOptions )();
bool(*create)(
CaptureGetPointerBuffer getPointerBufferFn,

View File

@@ -236,6 +236,7 @@ static CaptureResult xcb_getFrame(FrameBuffer * frame, const unsigned int height
struct CaptureInterface Capture_XCB =
{
.shortName = "XCB",
.asyncCapture = true,
.getName = xcb_getName,
.create = xcb_create,
.init = xcb_init,

View File

@@ -10,6 +10,7 @@ add_library(platform_Windows STATIC
src/service.c
src/mousehook.c
src/force_compose.c
src/delay.c
)
# allow use of functions for Windows Vista or later

View File

@@ -142,7 +142,7 @@ static void dxgi_initOptions(void)
{
.module = "dxgi",
.name = "useAcquireLock",
.description = "Enable locking around `AcquireFrame` (EXPERIMENTAL, leave enabled if you're not sure!)",
.description = "Enable locking around `AcquireNextFrame` (EXPERIMENTAL, leave enabled if you're not sure!)",
.type = OPTION_TYPE_BOOL,
.value.x_bool = true
},
@@ -230,7 +230,7 @@ static bool dxgi_init(void)
const char * optAdapter = option_get_string("dxgi", "adapter");
const char * optOutput = option_get_string("dxgi", "output" );
for(int i = 0; IDXGIFactory1_EnumAdapters1(this->factory, i, &this->adapter) != DXGI_ERROR_NOT_FOUND; ++i)
for (int i = 0; IDXGIFactory1_EnumAdapters1(this->factory, i, &this->adapter) != DXGI_ERROR_NOT_FOUND; ++i)
{
if (optAdapter)
{
@@ -259,7 +259,7 @@ static bool dxgi_init(void)
DEBUG_INFO("Adapter matched, trying: %ls", adapterDesc.Description);
}
for(int n = 0; IDXGIAdapter1_EnumOutputs(this->adapter, n, &this->output) != DXGI_ERROR_NOT_FOUND; ++n)
for (int n = 0; IDXGIAdapter1_EnumOutputs(this->adapter, n, &this->output) != DXGI_ERROR_NOT_FOUND; ++n)
{
IDXGIOutput_GetDesc(this->output, &outputDesc);
if (optOutput)
@@ -302,6 +302,16 @@ static bool dxgi_init(void)
goto fail;
}
DXGI_ADAPTER_DESC1 adapterDesc;
IDXGIAdapter1_GetDesc1(this->adapter, &adapterDesc);
DEBUG_INFO("Device Name : %ls" , outputDesc.DeviceName);
DEBUG_INFO("Device Descripion: %ls" , adapterDesc.Description);
DEBUG_INFO("Device Vendor ID : 0x%x" , adapterDesc.VendorId);
DEBUG_INFO("Device Device ID : 0x%x" , adapterDesc.DeviceId);
DEBUG_INFO("Device Video Mem : %u MiB" , (unsigned)(adapterDesc.DedicatedVideoMemory / 1048576));
DEBUG_INFO("Device Sys Mem : %u MiB" , (unsigned)(adapterDesc.DedicatedSystemMemory / 1048576));
DEBUG_INFO("Shared Sys Mem : %u MiB" , (unsigned)(adapterDesc.SharedSystemMemory / 1048576));
static const D3D_FEATURE_LEVEL win8[] =
{
D3D_FEATURE_LEVEL_11_1,
@@ -368,9 +378,6 @@ static bool dxgi_init(void)
goto fail;
}
DXGI_ADAPTER_DESC1 adapterDesc;
IDXGIAdapter1_GetDesc1(this->adapter, &adapterDesc);
switch(outputDesc.Rotation)
{
case DXGI_MODE_ROTATION_ROTATE90:
@@ -411,12 +418,6 @@ static bool dxgi_init(void)
this->dpi = monitor_dpi(outputDesc.Monitor);
++this->formatVer;
DEBUG_INFO("Device Descripion: %ls" , adapterDesc.Description);
DEBUG_INFO("Device Vendor ID : 0x%x" , adapterDesc.VendorId);
DEBUG_INFO("Device Device ID : 0x%x" , adapterDesc.DeviceId);
DEBUG_INFO("Device Video Mem : %u MiB" , (unsigned)(adapterDesc.DedicatedVideoMemory / 1048576));
DEBUG_INFO("Device Sys Mem : %u MiB" , (unsigned)(adapterDesc.DedicatedSystemMemory / 1048576));
DEBUG_INFO("Shared Sys Mem : %u MiB" , (unsigned)(adapterDesc.SharedSystemMemory / 1048576));
DEBUG_INFO("Feature Level : 0x%x" , this->featureLevel);
DEBUG_INFO("Capture Size : %u x %u", this->width, this->height);
DEBUG_INFO("AcquireLock : %s" , this->useAcquireLock ? "enabled" : "disabled");
@@ -542,7 +543,7 @@ static bool dxgi_init(void)
texDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
texDesc.MiscFlags = 0;
for(int i = 0; i < this->maxTextures; ++i)
for (int i = 0; i < this->maxTextures; ++i)
{
status = ID3D11Device_CreateTexture2D(this->device, &texDesc, NULL, &this->texture[i].tex);
if (FAILED(status))
@@ -583,7 +584,7 @@ static bool dxgi_deinit(void)
{
assert(this);
for(int i = 0; i < this->maxTextures; ++i)
for (int i = 0; i < this->maxTextures; ++i)
{
this->texture[i].state = TEXTURE_STATE_UNUSED;
@@ -764,7 +765,7 @@ static CaptureResult dxgi_capture(void)
uint32_t bufferSize;
if (frameInfo.PointerShapeBufferSize > 0)
{
if(!this->getPointerBufferFn(&pointerShape, &bufferSize))
if (!this->getPointerBufferFn(&pointerShape, &bufferSize))
DEBUG_WARN("Failed to obtain a buffer for the pointer shape");
else
copyPointer = true;
@@ -881,13 +882,13 @@ static CaptureResult dxgi_waitFrame(CaptureFrame * frame, const size_t maxFrameS
assert(this->initialized);
// NOTE: the event may be signaled when there are no frames available
if(atomic_load_explicit(&this->texReady, memory_order_acquire) == 0)
if (atomic_load_explicit(&this->texReady, memory_order_acquire) == 0)
{
if (!lgWaitEvent(this->frameEvent, 1000))
return CAPTURE_RESULT_TIMEOUT;
// the count will still be zero if we are stopping
if(atomic_load_explicit(&this->texReady, memory_order_acquire) == 0)
if (atomic_load_explicit(&this->texReady, memory_order_acquire) == 0)
return CAPTURE_RESULT_TIMEOUT;
}
@@ -996,6 +997,7 @@ static CaptureResult dxgi_releaseFrame(void)
struct CaptureInterface Capture_DXGI =
{
.shortName = "DXGI",
.asyncCapture = true,
.getName = dxgi_getName,
.initOptions = dxgi_initOptions,
.create = dxgi_create,

View File

@@ -57,7 +57,6 @@ struct iface
NvFBCFrameGrabInfo grabInfo;
LGEvent * frameEvent;
LGEvent * cursorEvent;
int mouseX, mouseY, mouseHotX, mouseHotY;
@@ -134,13 +133,6 @@ static bool nvfbc_create(
return false;
this = (struct iface *)calloc(sizeof(struct iface), 1);
this->frameEvent = lgCreateEvent(true, 17);
if (!this->frameEvent)
{
DEBUG_ERROR("failed to create the frame event");
nvfbc_free();
return false;
}
this->seperateCursor = option_get_bool("nvfbc", "decoupleCursor");
this->getPointerBufferFn = getPointerBufferFn;
@@ -165,7 +157,7 @@ static bool nvfbc_init(void)
privDataLen = (bufferLen - 1) / 2;
privData = (uint8_t *)malloc(privDataLen);
char hex[3] = {0};
for(int i = 0; i < privDataLen; ++i)
for (int i = 0; i < privDataLen; ++i)
{
memcpy(hex, &buffer[i*2], 2);
privData[i] = (uint8_t)strtoul(hex, NULL, 16);
@@ -186,7 +178,6 @@ static bool nvfbc_init(void)
free(privData);
getDesktopSize(&this->width, &this->height, &this->dpi);
lgResetEvent(this->frameEvent);
HANDLE event;
if (!NvFBCToSysSetup(
@@ -239,7 +230,6 @@ static void nvfbc_stop(void)
this->stop = true;
lgSignalEvent(this->cursorEvent);
lgSignalEvent(this->frameEvent);
if (this->pointerThread)
{
@@ -262,9 +252,6 @@ static bool nvfbc_deinit(void)
static void nvfbc_free(void)
{
if (this->frameEvent)
lgFreeEvent(this->frameEvent);
if (this->mouseHookCreated)
mouseHook_remove();
@@ -300,28 +287,25 @@ static CaptureResult nvfbc_capture(void)
bool changed = false;
const unsigned int h = (this->height + 127) / 128;
const unsigned int w = (this->width + 127) / 128;
for(unsigned int y = 0; y < h; ++y)
for(unsigned int x = 0; x < w; ++x)
for (unsigned int y = 0; y < h; ++y)
for (unsigned int x = 0; x < w; ++x)
if (this->diffMap[(y*w)+x])
{
changed = true;
break;
goto done;
}
done:
if (!changed)
return CAPTURE_RESULT_TIMEOUT;
memcpy(&this->grabInfo, &grabInfo, sizeof(grabInfo));
lgSignalEvent(this->frameEvent);
return CAPTURE_RESULT_OK;
}
static CaptureResult nvfbc_waitFrame(CaptureFrame * frame,
const size_t maxFrameSize)
{
if (!lgWaitEvent(this->frameEvent, 1000))
return CAPTURE_RESULT_TIMEOUT;
if (this->stop)
return CAPTURE_RESULT_REINIT;
@@ -423,6 +407,7 @@ static int pointerThread(void * unused)
struct CaptureInterface Capture_NVFBC =
{
.shortName = "NvFBC",
.asyncCapture = false,
.getName = nvfbc_getName,
.initOptions = nvfbc_initOptions,

View File

@@ -253,6 +253,11 @@ CaptureResult NvFBCToSysCapture(
handle->retry = 0;
break;
case NVFBC_ERROR_PROTECTED_CONTENT:
DEBUG_WARN("Protected content is playing, can't capture");
Sleep(100);
return CAPTURE_RESULT_TIMEOUT;
case NVFBC_ERROR_INVALID_PARAM:
if (handle->retry < 2)
{

View File

@@ -0,0 +1,34 @@
/**
* Looking Glass
* Copyright (C) 2017-2021 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 <windows.h>
typedef NTSTATUS (__stdcall *ZwSetTimerResolution_t)(ULONG RequestedResolution,
BOOLEAN Set, PULONG ActualResolution);
extern ZwSetTimerResolution_t ZwSetTimerResolution;
typedef NTSTATUS (__stdcall *NtDelayExecution_t)(BOOL Alertable,
PLARGE_INTEGER DelayInterval);
extern NtDelayExecution_t NtDelayExecution;
void delayInit(void);
// like sleep but more accurate
void delayExecution(float ms);

View File

@@ -0,0 +1,49 @@
/**
* Looking Glass
* Copyright (C) 2017-2021 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 "windows/delay.h"
#include "common/debug.h"
NtDelayExecution_t NtDelayExecution;
ZwSetTimerResolution_t ZwSetTimerResolution;
void delayInit(void)
{
HMODULE ntdll = GetModuleHandle("ntdll.dll");
NtDelayExecution = (NtDelayExecution_t)
GetProcAddress(ntdll, "NtDelayExecution");
// Increase the timer resolution
ZwSetTimerResolution = (ZwSetTimerResolution_t)
GetProcAddress(ntdll, "ZwSetTimerResolution");
if (ZwSetTimerResolution)
{
ULONG actualResolution;
ZwSetTimerResolution(1, true, &actualResolution);
DEBUG_INFO("System timer resolution: %lu ns", actualResolution * 100);
}
}
void delayExecution(float ms)
{
LARGE_INTEGER interval = { .QuadPart = -1 * (int)(ms * 10000.0f) };
NtDelayExecution(FALSE, &interval);
}

View File

@@ -19,6 +19,7 @@
*/
#include "windows/mousehook.h"
#include "windows/delay.h"
#include "common/windebug.h"
#include "platform.h"
@@ -31,8 +32,8 @@ struct mouseHook
HHOOK hook;
MouseHookFn callback;
int x, y;
HANDLE event;
HANDLE thread;
HANDLE event , updateEvent;
HANDLE thread, updateThread;
};
static struct mouseHook mouseHook = { 0 };
@@ -85,6 +86,31 @@ static VOID WINAPI winEventProc(HWINEVENTHOOK hWinEventHook, DWORD event,
}
}
static DWORD WINAPI updateThreadProc(LPVOID lParam)
{
HANDLE events[2] = {
mouseHook.event,
mouseHook.updateEvent
};
while(true)
{
switch(WaitForMultipleObjects(2, events, FALSE, INFINITE))
{
case WAIT_OBJECT_0:
DEBUG_INFO("Mouse hook update thread received quit request");
return 0;
case WAIT_OBJECT_0 + 1:
mouseHook.callback(mouseHook.x, mouseHook.y);
// limit this to 1000Hz, who has a mouse that updates faster anyway?
delayExecution(1.0f);
break;
}
}
}
static DWORD WINAPI threadProc(LPVOID lParam) {
if (mouseHook.installed)
{
@@ -143,11 +169,28 @@ void mouseHook_install(MouseHookFn callback)
mouseHook.event = CreateEventA(NULL, FALSE, FALSE, NULL);
if (!mouseHook.event)
{
DEBUG_WINERROR("Failed to create mouse hook uninstall event", GetLastError());
DEBUG_WINERROR("Failed to create mouse hook uninstall event",
GetLastError());
return;
}
}
mouseHook.thread = CreateThread(NULL, 0, threadProc, callback, 0, NULL);
if (!mouseHook.updateEvent)
{
mouseHook.updateEvent = CreateEventA(NULL, FALSE, FALSE, NULL);
if (!mouseHook.event)
{
DEBUG_WINERROR("Failed to create mouse hook update event",
GetLastError());
return;
}
}
mouseHook.thread =
CreateThread(NULL, 0, threadProc, callback, 0, NULL);
mouseHook.updateThread =
CreateThread(NULL, 0, updateThreadProc, 0, 0, NULL);
}
void mouseHook_remove(void)
@@ -156,8 +199,10 @@ void mouseHook_remove(void)
return;
SetEvent(mouseHook.event);
WaitForSingleObject(mouseHook.thread, INFINITE);
WaitForSingleObject(mouseHook.thread , INFINITE);
WaitForSingleObject(mouseHook.updateThread, INFINITE);
CloseHandle(mouseHook.thread);
CloseHandle(mouseHook.updateThread);
}
static LRESULT WINAPI mouseHook_hook(int nCode, WPARAM wParam, LPARAM lParam)
@@ -169,7 +214,7 @@ static LRESULT WINAPI mouseHook_hook(int nCode, WPARAM wParam, LPARAM lParam)
{
mouseHook.x = msg->pt.x;
mouseHook.y = msg->pt.y;
mouseHook.callback(msg->pt.x, msg->pt.y);
SetEvent(mouseHook.updateEvent);
}
}
return CallNextHookEx(mouseHook.hook, nCode, wParam, lParam);

View File

@@ -20,6 +20,7 @@
#include "platform.h"
#include "service.h"
#include "windows/delay.h"
#include "windows/mousehook.h"
#include <windows.h>
@@ -61,10 +62,6 @@ struct AppState
static struct AppState app = {0};
HWND MessageHWND;
// undocumented API to adjust the system timer resolution (yes, its a nasty hack)
typedef NTSTATUS (__stdcall *ZwSetTimerResolution_t)(ULONG RequestedResolution, BOOLEAN Set, PULONG ActualResolution);
static ZwSetTimerResolution_t ZwSetTimerResolution = NULL;
// linux mingw64 is missing this
#ifndef MSGFLT_RESET
#define MSGFLT_RESET (0)
@@ -504,7 +501,7 @@ void boostPriority(void)
bool app_init(void)
{
const char * logFile = option_get_string("os", "logFile" );
const char * logFile = option_get_string("os", "logFile");
// redirect stderr to a file
if (logFile && strcmp(logFile, "stderr") != 0)
@@ -513,14 +510,7 @@ bool app_init(void)
// always flush stderr
setbuf(stderr, NULL);
// Increase the timer resolution
ZwSetTimerResolution = (ZwSetTimerResolution_t)GetProcAddress(GetModuleHandle("ntdll.dll"), "ZwSetTimerResolution");
if (ZwSetTimerResolution)
{
ULONG actualResolution;
ZwSetTimerResolution(1, true, &actualResolution);
DEBUG_INFO("System timer resolution: %.2f ns", (float)actualResolution / 100.0f);
}
delayInit();
// get the performance frequency for spinlocks
QueryPerformanceFrequency(&app.perfFreq);

View File

@@ -79,17 +79,21 @@ struct app
PLGMPHost lgmp;
PLGMPHostQueue pointerQueue;
PLGMPMemory pointerMemory[POINTER_SHAPE_BUFFERS];
PLGMPMemory pointerMemory[LGMP_Q_POINTER_LEN];
PLGMPMemory pointerShapeMemory[POINTER_SHAPE_BUFFERS];
LG_Lock pointerLock;
CapturePointer pointerInfo;
PLGMPMemory pointerShape;
bool pointerShapeValid;
unsigned int pointerIndex;
unsigned int pointerShapeIndex;
long pageSize;
size_t maxFrameSize;
PLGMPHostQueue frameQueue;
PLGMPMemory frameMemory[LGMP_Q_FRAME_LEN];
unsigned int frameIndex;
bool frameValid;
CaptureInterface * iface;
@@ -138,118 +142,127 @@ static bool lgmpTimer(void * opaque)
return true;
}
static bool sendFrame(void)
{
CaptureFrame frame = { 0 };
bool repeatFrame = false;
//wait until there is room in the queue
while(app.state == APP_STATE_RUNNING &&
lgmpHostQueuePending(app.frameQueue) == LGMP_Q_FRAME_LEN)
{
usleep(1);
continue;
}
if (app.state != APP_STATE_RUNNING)
return false;
switch(app.iface->waitFrame(&frame, app.maxFrameSize))
{
case CAPTURE_RESULT_OK:
break;
case CAPTURE_RESULT_REINIT:
{
app.state = APP_STATE_RESTART;
DEBUG_INFO("Frame thread reinit");
return false;
}
case CAPTURE_RESULT_ERROR:
{
DEBUG_ERROR("Failed to get the frame");
return false;
}
case CAPTURE_RESULT_TIMEOUT:
{
if (app.frameValid && lgmpHostQueueNewSubs(app.frameQueue) > 0)
{
// resend the last frame
repeatFrame = true;
break;
}
return true;
}
}
LGMP_STATUS status;
// if we are repeating a frame just send the last frame again
if (repeatFrame)
{
if ((status = lgmpHostQueuePost(app.frameQueue, 0, app.frameMemory[app.frameIndex])) != LGMP_OK)
DEBUG_ERROR("%s", lgmpStatusString(status));
return true;
}
// we increment the index first so that if we need to repeat a frame
// the index still points to the latest valid frame
if (++app.frameIndex == LGMP_Q_FRAME_LEN)
app.frameIndex = 0;
KVMFRFrame * fi = lgmpHostMemPtr(app.frameMemory[app.frameIndex]);
switch(frame.format)
{
case CAPTURE_FMT_BGRA : fi->type = FRAME_TYPE_BGRA ; break;
case CAPTURE_FMT_RGBA : fi->type = FRAME_TYPE_RGBA ; break;
case CAPTURE_FMT_RGBA10 : fi->type = FRAME_TYPE_RGBA10 ; break;
case CAPTURE_FMT_RGBA16F: fi->type = FRAME_TYPE_RGBA16F; break;
default:
DEBUG_ERROR("Unsupported frame format %d, skipping frame", frame.format);
return true;
}
switch(frame.rotation)
{
case CAPTURE_ROT_0 : fi->rotation = FRAME_ROT_0 ; break;
case CAPTURE_ROT_90 : fi->rotation = FRAME_ROT_90 ; break;
case CAPTURE_ROT_180: fi->rotation = FRAME_ROT_180; break;
case CAPTURE_ROT_270: fi->rotation = FRAME_ROT_270; break;
default:
DEBUG_WARN("Unsupported frame rotation %d", frame.rotation);
fi->rotation = FRAME_ROT_0;
break;
}
fi->formatVer = frame.formatVer;
fi->width = frame.width;
fi->height = frame.height;
fi->realHeight = frame.realHeight;
fi->stride = frame.stride;
fi->pitch = frame.pitch;
fi->offset = app.pageSize - FrameBufferStructSize;
fi->mouseScalePercent = app.iface->getMouseScale();
fi->blockScreensaver = os_blockScreensaver();
app.frameValid = true;
// put the framebuffer on the border of the next page
// this is to allow for aligned DMA transfers by the receiver
FrameBuffer * fb = (FrameBuffer *)(((uint8_t*)fi) + fi->offset);
framebuffer_prepare(fb);
/* we post and then get the frame, this is intentional! */
if ((status = lgmpHostQueuePost(app.frameQueue, 0, app.frameMemory[app.frameIndex])) != LGMP_OK)
{
DEBUG_ERROR("%s", lgmpStatusString(status));
return true;
}
app.iface->getFrame(fb, frame.height);
return true;
}
static int frameThread(void * opaque)
{
DEBUG_INFO("Frame thread started");
bool frameValid = false;
bool repeatFrame = false;
CaptureFrame frame = { 0 };
const long pageSize = sysinfo_getPageSize();
while(app.state == APP_STATE_RUNNING)
{
//wait until there is room in the queue
if(lgmpHostQueuePending(app.frameQueue) == LGMP_Q_FRAME_LEN)
{
usleep(1);
continue;
}
switch(app.iface->waitFrame(&frame, app.maxFrameSize))
{
case CAPTURE_RESULT_OK:
repeatFrame = false;
break;
case CAPTURE_RESULT_REINIT:
{
app.state = APP_STATE_RESTART;
DEBUG_INFO("Frame thread reinit");
return 0;
}
case CAPTURE_RESULT_ERROR:
{
DEBUG_ERROR("Failed to get the frame");
return 0;
}
case CAPTURE_RESULT_TIMEOUT:
{
if (frameValid && lgmpHostQueueNewSubs(app.frameQueue) > 0)
{
// resend the last frame
repeatFrame = true;
break;
}
continue;
}
}
LGMP_STATUS status;
// if we are repeating a frame just send the last frame again
if (repeatFrame)
{
if ((status = lgmpHostQueuePost(app.frameQueue, 0, app.frameMemory[app.frameIndex])) != LGMP_OK)
DEBUG_ERROR("%s", lgmpStatusString(status));
continue;
}
// we increment the index first so that if we need to repeat a frame
// the index still points to the latest valid frame
if (++app.frameIndex == LGMP_Q_FRAME_LEN)
app.frameIndex = 0;
KVMFRFrame * fi = lgmpHostMemPtr(app.frameMemory[app.frameIndex]);
switch(frame.format)
{
case CAPTURE_FMT_BGRA : fi->type = FRAME_TYPE_BGRA ; break;
case CAPTURE_FMT_RGBA : fi->type = FRAME_TYPE_RGBA ; break;
case CAPTURE_FMT_RGBA10 : fi->type = FRAME_TYPE_RGBA10 ; break;
case CAPTURE_FMT_RGBA16F: fi->type = FRAME_TYPE_RGBA16F; break;
default:
DEBUG_ERROR("Unsupported frame format %d, skipping frame", frame.format);
continue;
}
switch(frame.rotation)
{
case CAPTURE_ROT_0 : fi->rotation = FRAME_ROT_0 ; break;
case CAPTURE_ROT_90 : fi->rotation = FRAME_ROT_90 ; break;
case CAPTURE_ROT_180: fi->rotation = FRAME_ROT_180; break;
case CAPTURE_ROT_270: fi->rotation = FRAME_ROT_270; break;
default:
DEBUG_WARN("Unsupported frame rotation %d", frame.rotation);
fi->rotation = FRAME_ROT_0;
break;
}
fi->formatVer = frame.formatVer;
fi->width = frame.width;
fi->height = frame.height;
fi->realHeight = frame.realHeight;
fi->stride = frame.stride;
fi->pitch = frame.pitch;
fi->offset = pageSize - FrameBufferStructSize;
fi->mouseScalePercent = app.iface->getMouseScale();
fi->blockScreensaver = os_blockScreensaver();
frameValid = true;
// put the framebuffer on the border of the next page
// this is to allow for aligned DMA transfers by the receiver
FrameBuffer * fb = (FrameBuffer *)(((uint8_t*)fi) + fi->offset);
framebuffer_prepare(fb);
/* we post and then get the frame, this is intentional! */
if ((status = lgmpHostQueuePost(app.frameQueue, 0, app.frameMemory[app.frameIndex])) != LGMP_OK)
{
DEBUG_ERROR("%s", lgmpStatusString(status));
continue;
}
app.iface->getFrame(fb, frame.height);
if (!sendFrame())
break;
}
DEBUG_INFO("Frame thread stopped");
return 0;
@@ -258,6 +271,9 @@ static int frameThread(void * opaque)
bool startThreads(void)
{
app.state = APP_STATE_RUNNING;
if (!app.iface->asyncCapture)
return true;
if (!lgCreateThread("FrameThread", frameThread, NULL, &app.frameThread))
{
DEBUG_ERROR("Failed to create the frame thread");
@@ -269,20 +285,25 @@ bool startThreads(void)
bool stopThreads(void)
{
bool ok = true;
app.iface->stop();
if (app.state != APP_STATE_SHUTDOWN)
app.state = APP_STATE_IDLE;
if (app.frameThread && !lgJoinThread(app.frameThread, NULL))
{
DEBUG_WARN("Failed to join the frame thread");
ok = false;
}
app.frameThread = NULL;
if (!app.iface->asyncCapture)
return true;
return ok;
if (app.frameThread)
{
if (!lgJoinThread(app.frameThread, NULL))
{
DEBUG_WARN("Failed to join the frame thread");
app.frameThread = NULL;
return false;
}
app.frameThread = NULL;
}
return true;
}
static bool captureStart(void)
@@ -315,8 +336,8 @@ static bool captureStop(void)
bool captureGetPointerBuffer(void ** data, uint32_t * size)
{
PLGMPMemory mem = app.pointerMemory[app.pointerIndex];
*data = ((uint8_t*)lgmpHostMemPtr(mem)) + sizeof(KVMFRCursor);
PLGMPMemory mem = app.pointerShapeMemory[app.pointerShapeIndex];
*data = (uint8_t*)lgmpHostMemPtr(mem) + sizeof(KVMFRCursor);
*size = MAX_POINTER_SIZE - sizeof(KVMFRCursor);
return true;
}
@@ -342,8 +363,18 @@ static void sendPointer(bool newClient)
// new clients need the last known shape and current position
if (newClient)
{
PLGMPMemory mem;
if (app.pointerShapeValid)
mem = app.pointerShape;
else
{
mem = app.pointerMemory[app.pointerIndex];
if (++app.pointerIndex == LGMP_Q_POINTER_LEN)
app.pointerIndex = 0;
}
// update the saved details with the current cursor position
KVMFRCursor *cursor = lgmpHostMemPtr(app.pointerShape);
KVMFRCursor *cursor = lgmpHostMemPtr(mem);
cursor->x = app.pointerInfo.x;
cursor->y = app.pointerInfo.y;
@@ -351,12 +382,24 @@ static void sendPointer(bool newClient)
(app.pointerShapeValid ? CURSOR_FLAG_SHAPE : 0) |
(app.pointerInfo.visible ? CURSOR_FLAG_VISIBLE : 0);
postPointer(flags, app.pointerShape);
postPointer(flags, mem);
return;
}
uint32_t flags = 0;
PLGMPMemory mem = app.pointerMemory[app.pointerIndex];
PLGMPMemory mem;
if (app.pointerInfo.shapeUpdate)
{
mem = app.pointerShapeMemory[app.pointerShapeIndex];
if (++app.pointerShapeIndex == POINTER_SHAPE_BUFFERS)
app.pointerShapeIndex = 0;
}
else
{
mem = app.pointerMemory[app.pointerIndex];
if (++app.pointerIndex == LGMP_Q_POINTER_LEN)
app.pointerIndex = 0;
}
KVMFRCursor *cursor = lgmpHostMemPtr(mem);
if (app.pointerInfo.positionUpdate || newClient)
@@ -390,16 +433,9 @@ static void sendPointer(bool newClient)
app.pointerShapeValid = true;
flags |= CURSOR_FLAG_SHAPE;
// retain this memory for new clients
app.pointerMemory[app.pointerIndex] = app.pointerShape;
app.pointerShape = mem;
}
// only advance if the pointer shape was not swapped out of the list
if ((flags & CURSOR_FLAG_SHAPE) == 0)
if (++app.pointerIndex == POINTER_SHAPE_BUFFERS)
app.pointerIndex = 0;
postPointer(flags, mem);
}
@@ -525,34 +561,41 @@ int app_main(int argc, char * argv[])
goto fail_lgmp;
}
for(int i = 0; i < POINTER_SHAPE_BUFFERS; ++i)
for(int i = 0; i < LGMP_Q_POINTER_LEN; ++i)
{
if ((status = lgmpHostMemAlloc(app.lgmp, MAX_POINTER_SIZE, &app.pointerMemory[i])) != LGMP_OK)
if ((status = lgmpHostMemAlloc(app.lgmp, sizeof(KVMFRCursor), &app.pointerMemory[i])) != LGMP_OK)
{
DEBUG_ERROR("lgmpHostMemAlloc Failed (Pointer): %s", lgmpStatusString(status));
exitcode = LG_HOST_EXIT_FATAL;
goto fail_lgmp;
}
memset(lgmpHostMemPtr(app.pointerMemory[i]), 0, MAX_POINTER_SIZE);
memset(lgmpHostMemPtr(app.pointerMemory[i]), 0, sizeof(KVMFRCursor));
}
app.pointerShapeValid = false;
if ((status = lgmpHostMemAlloc(app.lgmp, MAX_POINTER_SIZE, &app.pointerShape)) != LGMP_OK)
for(int i = 0; i < POINTER_SHAPE_BUFFERS; ++i)
{
DEBUG_ERROR("lgmpHostMemAlloc Failed (Pointer Shape): %s", lgmpStatusString(status));
exitcode = LG_HOST_EXIT_FATAL;
goto fail_lgmp;
if ((status = lgmpHostMemAlloc(app.lgmp, MAX_POINTER_SIZE, &app.pointerShapeMemory[i])) != LGMP_OK)
{
DEBUG_ERROR("lgmpHostMemAlloc Failed (Pointer Shapes): %s", lgmpStatusString(status));
exitcode = LG_HOST_EXIT_FATAL;
goto fail_lgmp;
}
memset(lgmpHostMemPtr(app.pointerShapeMemory[i]), 0, MAX_POINTER_SIZE);
}
const long sz = sysinfo_getPageSize();
app.pageSize = sysinfo_getPageSize();
app.frameValid = false;
app.pointerShapeValid = false;
app.maxFrameSize = lgmpHostMemAvail(app.lgmp);
app.maxFrameSize = (app.maxFrameSize - (sz - 1)) & ~(sz - 1);
app.maxFrameSize = (app.maxFrameSize - (app.pageSize - 1)) & ~(app.pageSize - 1);
app.maxFrameSize /= LGMP_Q_FRAME_LEN;
DEBUG_INFO("Max Frame Size : %u MiB", (unsigned int)(app.maxFrameSize / 1048576LL));
for(int i = 0; i < LGMP_Q_FRAME_LEN; ++i)
{
if ((status = lgmpHostMemAllocAligned(app.lgmp, app.maxFrameSize, sz, &app.frameMemory[i])) != LGMP_OK)
if ((status = lgmpHostMemAllocAligned(app.lgmp, app.maxFrameSize,
app.pageSize, &app.frameMemory[i])) != LGMP_OK)
{
DEBUG_ERROR("lgmpHostMemAlloc Failed (Frame): %s", lgmpStatusString(status));
exitcode = LG_HOST_EXIT_FATAL;
@@ -594,6 +637,8 @@ int app_main(int argc, char * argv[])
}
DEBUG_INFO("Using : %s", iface->getName());
DEBUG_INFO("Capture Method : %s", iface->asyncCapture ?
"Asynchronous" : "Synchronous");
app.state = APP_STATE_RUNNING;
app.iface = iface;
@@ -688,6 +733,9 @@ int app_main(int argc, char * argv[])
exitcode = LG_HOST_EXIT_FAILED;
goto fail_capture;
}
if (!iface->asyncCapture)
sendFrame();
}
if (app.state != APP_STATE_SHUTDOWN)
@@ -728,9 +776,10 @@ fail_timer:
fail_lgmp:
for(int i = 0; i < LGMP_Q_FRAME_LEN; ++i)
lgmpHostMemFree(&app.frameMemory[i]);
for(int i = 0; i < POINTER_SHAPE_BUFFERS; ++i)
for(int i = 0; i < LGMP_Q_POINTER_LEN; ++i)
lgmpHostMemFree(&app.pointerMemory[i]);
lgmpHostMemFree(&app.pointerShape);
for(int i = 0; i < POINTER_SHAPE_BUFFERS; ++i)
lgmpHostMemFree(&app.pointerShapeMemory[i]);
lgmpHostFree(&app.lgmp);
fail_ivshmem:

View File

@@ -22,6 +22,8 @@
#include <common/version.h>
#include <stdio.h>
#include "common/debug.h"
#ifdef _WIN32
#undef EXPORT
#define EXPORT __declspec(dllexport)
@@ -38,6 +40,7 @@ extern struct obs_source_info lg_source;
MODULE_EXPORT bool obs_module_load(void)
{
debug_init();
printf("Looking Glass OBS Client (%s)\n", BUILD_VERSION);
obs_register_source(&lg_source);
return true;