Compare commits

..

48 Commits

Author SHA1 Message Date
Geoffrey McRae
76710ef201 [all] updated issue template and readme in preperation for B2 2020-10-08 20:04:52 +11:00
Geoffrey McRae
e20c8a5cc7 [host] dxgi: don't try to get the hotspot of a null cursor 2020-10-06 23:24:01 +11:00
Geoffrey McRae
4f4d2dbf42 [host] dxgi: fix memory leak if an error occurs 2020-10-06 22:32:10 +11:00
Geoffrey McRae
8692e9af80 [client] don't hide the cursor when SPICE is disabled
Fixes #304
2020-08-21 15:40:22 +10:00
Geoffrey McRae
7d2b39058c [client] ensure the cursor is updated when the window looses/gains focus 2020-08-20 16:05:55 +10:00
Geoffrey McRae
6927dbecd2 [client] added new input:mouseRedraw option
This new option, when enabled (the default) enables cursor movements to
trigger frame updates in the client, improving responsiveness at the
cost of increased FPS while the mouse is moving around.
2020-08-20 15:50:33 +10:00
Geoffrey McRae
f9b6dcc986 [client] only resync the timer if we got an early frame
This prevents a slow update (ie, 30ups) from pulling the refresh rate
below the minimum (ie, 60fps).
2020-08-20 15:18:45 +10:00
Geoffrey McRae
5c912e3c27 [client] spice: improve mouse syncronization with the host 2020-08-20 14:52:24 +10:00
Geoffrey McRae
7e362050f7 [all] update KVMFR to provide cursor hotspot information
This commit bumps the KVMFR protocol version as it adds additional
hotspot x & y fields to the KVMFRCursor struct. This corrects the issue
of invalid alignment of the local mouse when the shape has an offset
such as the 'I' beam.
2020-08-20 13:51:01 +10:00
Ash
10fbdeb294 update client/README.md: spice:captureOnStart from #278 2020-08-19 23:08:34 +10:00
camr0
72d70e8322 Update host/README.md: c-host -> host 2020-08-17 11:44:52 +10:00
Geoffrey McRae
c66a339bbc [client] egl: ensure overflow occurs for state value checks 2020-08-15 22:39:10 +10:00
Geoffrey McRae
1c7961daeb [host] dxgi: rework locking and retry logic for lower latency 2020-08-15 20:49:49 +10:00
Geoffrey McRae
cdc3384883 [host] dxgi: improve frame signaling mechanics 2020-08-15 18:16:11 +10:00
Geoffrey McRae
969effedde [host] update information about PsExec now LG can run as a service 2020-08-13 11:41:16 +10:00
Geoffrey McRae
dc4d1d49fa [host] updated the readme with regards to log file location 2020-08-12 22:15:22 +10:00
Geoffrey McRae
4e1f947a09 [host] Windows: fix uninstaller product name 2020-08-12 22:03:10 +10:00
Geoffrey McRae
15d1a74291 [host] Windows: multiple fixes to the installer 2020-08-12 21:50:48 +10:00
TheCakeIsNaOH
7dba6b9b08 [Host] Convert installer to setup service instead of scheduled task 2020-08-12 21:32:15 +10:00
TheCakeIsNaOH
a5ad531004 [Host] Change default install dir "Looking-Glass" to "Looking Glass" 2020-08-12 21:32:15 +10:00
TheCakeIsNaOH
c119b3dcca [Host] Correct installer and shortcut names 2020-08-12 21:32:15 +10:00
TheCakeIsNaOH
e2f2437ef4 [Host] Installer command line options and install location selection add 2020-08-12 21:32:15 +10:00
TheCakeIsNaOH
b2980fea63 [Host] Add instructions on how to build NSIS installer. 2020-08-12 21:32:15 +10:00
TheCakeIsNaOH
2b518690b8 [Host] NSIS script change names from C-Host to Host 2020-08-12 21:32:15 +10:00
TheCakeIsNaOH
92aca75792 [c-host] Add NSIS installer script 2020-08-12 21:32:15 +10:00
Geoffrey McRae
64fdb8b7bb [host] Windows: service (un)install now starts/stops the service
In addition to starting and stopping the service, it now also stops the
LG process if the service started it.
2020-08-12 20:56:02 +10:00
Geoffrey McRae
431ae3fc55 [common] linux: fix issue with infinite timeout events 2020-08-11 19:31:11 +10:00
Geoffrey McRae
380b5df9f9 [host] increase sleep timeout to 100ms 2020-08-11 19:11:17 +10:00
Geoffrey McRae
c7330167cf [host] shutdown capture if there are no subscribers
Fixes #33
2020-08-11 18:30:47 +10:00
Geoffrey McRae
ca02e1aba9 [host] Windows: change "Open Log File" to "Log File Location" 2020-08-11 17:45:00 +10:00
Geoffrey McRae
ca4b1f5592 [host] Windows: don't open the log file, instead show it's location
Now that it's recommended to run LG as the `SYSTEM` user, launching an
application to read the log file is dangerous as it will be launched
with the same access rights (`SYSTEM`). Instead so as Microsoft
recommends and only present a message box with the information.
2020-08-11 17:42:00 +10:00
Geoffrey McRae
0cf1e27709 [host] Windows: run with HIGH priority if started by the service 2020-08-11 17:37:40 +10:00
Geoffrey McRae
045932ce77 [host] send the correct cursor shape on client connection 2020-08-11 17:16:54 +10:00
Geoffrey McRae
bf5481446b [host] Windows: poll more freqently for a stopped LG process 2020-08-11 15:22:29 +10:00
Geoffrey McRae
e3f97e384b [client] rework the start/restart logic to use an enum 2020-08-11 15:14:58 +10:00
Geoffrey McRae
76e119f8ad [client] egl: don't fade the splash when restarting 2020-08-11 14:54:48 +10:00
Geoffrey McRae
bfb12c74fb [client] be quicker at detecting restart and quieter about it 2020-08-11 14:52:22 +10:00
Geoffrey McRae
fa50b7824c [client] fix crash on shutdown while waiting for a restart 2020-08-11 14:45:43 +10:00
Geoffrey McRae
da8b2d0cec [client] egl: properly wait for a new frame on restart 2020-08-11 14:45:08 +10:00
Geoffrey McRae
74649ddb96 [client] gracefully restart if the host application restarts 2020-08-11 14:30:44 +10:00
Geoffrey McRae
4619ddef5d [host] Windows: added missing linker library 2020-08-11 13:15:18 +10:00
Geoffrey McRae
ea74ee6e25 [host] windows: fix crosscompile take 2 2020-08-11 13:11:42 +10:00
Geoffrey McRae
ecd73aa670 [host] windows: fix linux crosscompile 2020-08-11 13:07:23 +10:00
Geoffrey McRae
10d9678b3d [host] Windows: improved service restart detection 2020-08-11 12:47:50 +10:00
Geoffrey McRae
e08d3afdbc [host] Windows: added missing service files 2020-08-11 12:27:04 +10:00
Geoffrey McRae
9a6b598438 [host] Windows: Implemented service to launch LG as the SYSTEM user
Experimental, use at your own peril!

This commit adds the ability for the LG host to install and launch with
Windows as a system service.

To install simply run `looking-glass-host.exe InstallService` or
conversely to uninstall `looking-glass-host.exe UninstallService`.
2020-08-11 12:22:22 +10:00
Geoffrey McRae
d9a80b16f0 [common] properly define _GNU_SOURCE and set the thread names 2020-08-10 16:22:02 +10:00
Geoffrey McRae
90d0cd873d [common] added a sleep to the framebuffer spinlock and a sane timeout 2020-08-10 16:18:08 +10:00
29 changed files with 1564 additions and 303 deletions

View File

@@ -1,11 +1,69 @@
### Required information
### Issues are for Bug Reports and Feature Requests Only!
Host CPU:
Host GPU:
Guest GPU:
Host Kernel version:
Host QEMU version:
If you are looking for help or support please use one of the following methods
Please describe what were you doing when the problem occured. If the Windows host application crashed please check for file named `looking-glass-host.dmp` and attach it to this bug report.
Create a New Topic on the Level1Tech's forum under the Looking Glass category:
* https://forum.level1techs.com/c/software/lookingGlass/142
**Reports that do not include this information will be ignored and closed**
Ask for help in #looking-glass in the VFIO discord server
* https://discord.gg/4ahCn4c
*Issues that are not bug reports or feature requests will be closed & ignored*
### Errors that are not bugs
Some errors generated by the LG client are not bugs, but rather issues with your
system's configuration and/or timing. Please do not report these, but rather use
one of the above resources to ask for advice/help.
* `LGMP_ERR_QUEUE_UNSUBSCRIBED` - Failure to heed advice on things such as
using `isolcpus` and CPU pinning may result in this message, especially if you
are over-taxing your CPU.
* `Could not create an SDL window: *` - Failure to create a SDL window is not an
issue with Looking Glass but rather a more substantial issue with your system,
such as missing hardware support for the RGBA32 pixmap format, or missing
required OpenGL EGL features.
* `The host application is not compatible with this client` - The Looking Glass
Host application in Windows is the incorrect version and is not compatible,
you need to make sure you run matching versions of both the host and client
applications.
### Bug Report Required Information
The entire (not truncated) output from the client application (if applicable).
To obtain this run `looking-glass-client` in a terminal.
```
PASTE CLIENT OUTPUT HERE
```
The entire (not truncated) log file from the host application (if applicable).
To obtain this locate the log file on your system, it will be in one of the
following two locations depending on how you are launching the Looking Glass Host
application:
* C:\Windows\Temp\looking-glass.txt
* C:\Users\YOUR_USER\AppData\Local\Temp\looking-glass.txt
This log may be quite long, please delete the file first and then proceed to
launch the host and reproduce the issue so that the log only contains the
pertinent information.
```
PASTE HOST LOG FILE CONTENTS HERE
```
If the client is unexpetedly exiting without a backtrace, please provide one via
gdb with the command `thread apply all bt`. If you are unsure how to do this
please watch the video below on how to perform a Debug build and generate this
backtrace.
https://www.youtube.com/watch?v=EqxxJK9Yo64
```
PASTE FULL BACKTRACE HERE
```

View File

@@ -4,6 +4,7 @@ An extremely low latency KVMFR (KVM FrameRelay) implementation for guests with
VGA PCI Passthrough.
* Project Website: https://looking-glass.hostfission.com
* Getting Started: https://looking-glass.hostfission.com/wiki/Installation
## Donations
@@ -23,18 +24,20 @@ support me directly using the following platforms.
** IMPORTANT **
This project contains submodules that must be checked out if building from the
git repository!
git repository! If you are not a developer and just want to compile Looking
Glass please download the source archive from the website instead:
https://looking-glass.hostfission.com/downloads
Please also be sure to see the following files for more information
Note: The `README.md` files are slowly being deprecated from this project in
favor of the wiki at https://looking-glass.hostfission.com/wiki, and as such the
information in these files may be dated.
* [client/README.md](client/README.md)
* [host/README.md](host/README.md)
* [module/README.md](module/README.md)
## Obtaining and using Looking Glass
Please see https://looking-glass.hostfission.com/wiki/
## Latest Version
If you would like to use the latest bleeding edge version of Looking Glass please

View File

@@ -1 +1 @@
B2-rc2-33-g2e1b0f2550+1
B2-rc4-11-g8692e9af80+1

View File

@@ -35,23 +35,6 @@ Should this all go well you should be left with the file `looking-glass-client`
## Usage Tips
### High priority capture using DXGI and Secure Desktop (UAC) capture support
By default Windows gives priority to the foreground application for any GPU
work which causes issues with capture if the foreground application is consuming
100% of the available GPU resources. The looking glass host application is able
to increase the kernel GPU thread to realtime priority which fixes this, but in
order to do so it must run as the `SYSTEM` user account. To do this, please use
`PsExec` from SysInternals (Microsoft), for example:
PsExec64.exe -s -i -d looking-glass-host.exe
This will also enable the host application to capture the secure desktop which
includes things like the lock screen and UAC prompts.
A future update (likely Beta 3) will include a service launcher for the Looking
Glass host which will remove the need for `PsExec`.
### Key Bindings
By default Looking Glass uses the `Scroll Lock` key as the escape key for commands as well as the input capture mode toggle, this can be changed using the `-m` switch if you desire a different key.
@@ -159,6 +142,7 @@ Command line arguments will override any options loaded from the config files.
| spice:clipboardToVM | | yes | Allow the clipboard to be syncronized TO the VM |
| spice:clipboardToLocal | | yes | Allow the clipboard to be syncronized FROM the VM |
| spice:scaleCursor | -j | yes | Scale cursor input position to screen size when up/down scaled |
| spice:captureOnStart | | no | Capture mouse and keyboard on start |
|------------------------------------------------------------------------------------------------------------------|
|--------------------------------------------------------------------------|

View File

@@ -33,6 +33,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
(x)->create && \
(x)->initialize && \
(x)->deinitialize && \
(x)->on_restart && \
(x)->on_resize && \
(x)->on_mouse_shape && \
(x)->on_mouse_event && \
@@ -87,6 +88,7 @@ typedef void (* LG_RendererSetup)();
typedef bool (* LG_RendererCreate )(void ** opaque, const LG_RendererParams params);
typedef bool (* LG_RendererInitialize )(void * opaque, Uint32 * sdlFlags);
typedef void (* LG_RendererDeInitialize)(void * opaque);
typedef void (* LG_RendererOnRestart )(void * opaque);
typedef void (* LG_RendererOnResize )(void * opaque, const int width, const int height, const LG_RendererRect destRect);
typedef bool (* LG_RendererOnMouseShape)(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data);
typedef bool (* LG_RendererOnMouseEvent)(void * opaque, const bool visible , const int x, const int y);
@@ -103,6 +105,7 @@ typedef struct LG_Renderer
LG_RendererCreate create;
LG_RendererInitialize initialize;
LG_RendererDeInitialize deinitialize;
LG_RendererOnRestart on_restart;
LG_RendererOnResize on_resize;
LG_RendererOnMouseShape on_mouse_shape;
LG_RendererOnMouseEvent on_mouse_event;

View File

@@ -68,6 +68,7 @@ struct Inst
EGL_Alert * alert; // the alert display
LG_RendererFormat format;
bool start;
uint64_t waitFadeTime;
bool waitDone;
@@ -225,6 +226,15 @@ void egl_deinitialize(void * opaque)
free(this);
}
void egl_on_restart(void * opaque)
{
struct Inst * this = (struct Inst *)opaque;
eglDestroyContext(this->display, this->frameContext);
this->frameContext = NULL;
this->start = false;
}
void egl_on_resize(void * opaque, const int width, const int height, const LG_RendererRect destRect)
{
struct Inst * this = (struct Inst *)opaque;
@@ -340,6 +350,7 @@ bool egl_on_frame_event(void * opaque, const LG_RendererFormat format, const Fra
return false;
}
this->start = true;
return true;
}
@@ -532,7 +543,10 @@ bool egl_render(void * opaque, SDL_Window * window)
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
if (egl_desktop_render(this->desktop, this->translateX, this->translateY, this->scaleX, this->scaleY, this->useNearest))
if (this->start && egl_desktop_render(this->desktop,
this->translateX, this->translateY,
this->scaleX , this->scaleY ,
this->useNearest))
{
if (!this->waitFadeTime)
this->waitFadeTime = microtime() + SPLASH_FADE_TIME;
@@ -559,6 +573,11 @@ bool egl_render(void * opaque, SDL_Window * window)
if (!this->waitDone)
egl_splash_render(this->splash, a, this->splashRatio);
}
else
{
if (!this->start)
egl_splash_render(this->splash, 1.0f, this->splashRatio);
}
if (this->showAlert)
{
@@ -595,6 +614,7 @@ struct LG_Renderer LGR_EGL =
.create = egl_create,
.initialize = egl_initialize,
.deinitialize = egl_deinitialize,
.on_restart = egl_on_restart,
.on_resize = egl_on_resize,
.on_mouse_shape = egl_on_mouse_shape,
.on_mouse_event = egl_on_mouse_event,

View File

@@ -319,7 +319,7 @@ bool egl_texture_update(EGL_Texture * texture, const uint8_t * buffer)
const uint8_t sw =
atomic_load_explicit(&texture->state.w, memory_order_acquire);
if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == sw + 1)
if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == (uint8_t)(sw + 1))
{
egl_warn_slow();
return true;
@@ -355,7 +355,7 @@ bool egl_texture_update_from_frame(EGL_Texture * texture, const FrameBuffer * fr
const uint8_t sw =
atomic_load_explicit(&texture->state.w, memory_order_acquire);
if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == sw + 1)
if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == (uint8_t)(sw + 1))
{
egl_warn_slow();
return true;
@@ -457,7 +457,7 @@ enum EGL_TexStatus egl_texture_bind(EGL_Texture * texture)
}
}
if (ss != sd && ss != sd+1)
if (ss != sd && ss != (uint8_t)(sd + 1))
sd = atomic_fetch_add_explicit(&texture->state.d, 1,
memory_order_release) + 1;
}

View File

@@ -295,6 +295,12 @@ void opengl_deinitialize(void * opaque)
free(this);
}
void opengl_on_restart(void * opaque)
{
struct Inst * this = (struct Inst *)opaque;
this->waiting = true;
}
void opengl_on_resize(void * opaque, const int width, const int height, const LG_RendererRect destRect)
{
struct Inst * this = (struct Inst *)opaque;
@@ -823,6 +829,7 @@ const LG_Renderer LGR_OpenGL =
.create = opengl_create,
.initialize = opengl_initialize,
.deinitialize = opengl_deinitialize,
.on_restart = opengl_on_restart,
.on_resize = opengl_on_resize,
.on_mouse_shape = opengl_on_mouse_shape,
.on_mouse_event = opengl_on_mouse_event,

View File

@@ -247,6 +247,13 @@ static struct Option options[] =
.type = OPTION_TYPE_INT,
.value.x_int = 0,
},
{
.module = "input",
.name = "mouseRedraw",
.description = "Mouse movements trigger redraws (ignores FPS minimum)",
.type = OPTION_TYPE_BOOL,
.value.x_bool = true,
},
// spice options
{
@@ -399,6 +406,7 @@ bool config_load(int argc, char * argv[])
params.escapeKey = option_get_int ("input", "escapeKey" );
params.hideMouse = option_get_bool ("input", "hideCursor" );
params.mouseSens = option_get_int ("input", "mouseSens" );
params.mouseRedraw = option_get_bool ("input", "mouseRedraw" );
params.minimizeOnFocusLoss = option_get_bool("win", "minimizeOnFocusLoss");

View File

@@ -141,7 +141,7 @@ static int renderThread(void * unused)
{
if (!state.lgr->render_startup(state.lgrData, state.window))
{
state.running = false;
state.state = APP_STATE_SHUTDOWN;
/* unblock threads waiting on the condition */
lgSignalEvent(e_startup);
@@ -155,7 +155,7 @@ static int renderThread(void * unused)
struct timespec time;
clock_gettime(CLOCK_REALTIME, &time);
while(state.running)
while(state.state != APP_STATE_SHUTDOWN)
{
if (state.frameTime > 0)
{
@@ -213,19 +213,23 @@ static int renderThread(void * unused)
if (atomic_fetch_sub_explicit(&a_framesPending, 1, memory_order_release) > 1)
continue;
if (lgWaitEventAbs(e_frame, &time))
if (lgWaitEventAbs(e_frame, &time) && state.frameTime > 0)
{
if (state.frameTime > 0)
/* only resync the timer if we got an early frame */
struct timespec now, diff;
clock_gettime(CLOCK_REALTIME, &now);
tsDiff(&diff, &now, &time);
if (diff.tv_sec == 0 && diff.tv_nsec < state.frameTime)
{
resyncCheck = 0;
clock_gettime(CLOCK_REALTIME, &time);
memcpy(&time, &now, sizeof(struct timespec));
tsAdd(&time, state.frameTime);
}
}
}
}
state.running = false;
state.state = APP_STATE_SHUTDOWN;
if (t_cursor)
lgJoinThread(t_cursor, NULL);
@@ -247,7 +251,7 @@ static int cursorThread(void * unused)
lgWaitEvent(e_startup, TIMEOUT_INFINITE);
// subscribe to the pointer queue
while(state.running)
while(state.state == APP_STATE_RUNNING)
{
status = lgmpClientSubscribe(state.lgmp, LGMP_Q_POINTER, &queue);
if (status == LGMP_OK)
@@ -260,11 +264,11 @@ static int cursorThread(void * unused)
}
DEBUG_ERROR("lgmpClientSubscribe Failed: %s", lgmpStatusString(status));
state.running = false;
state.state = APP_STATE_SHUTDOWN;
break;
}
while(state.running)
while(state.state == APP_STATE_RUNNING)
{
LGMPMessage msg;
if ((status = lgmpClientProcess(queue, &msg)) != LGMP_OK)
@@ -277,18 +281,25 @@ static int cursorThread(void * unused)
state.lgr->on_mouse_event
(
state.lgrData,
state.cursorVisible && state.drawCursor && state.cursorInView,
state.cursorVisible && state.drawCursor,
state.cursor.x,
state.cursor.y
);
lgSignalEvent(e_frame);
}
usleep(params.cursorPollInterval);
continue;
}
DEBUG_ERROR("lgmpClientProcess Failed: %s", lgmpStatusString(status));
state.running = false;
if (status == LGMP_ERR_INVALID_SESSION)
state.state = APP_STATE_RESTART;
else
{
DEBUG_ERROR("lgmpClientProcess Failed: %s", lgmpStatusString(status));
state.state = APP_STATE_SHUTDOWN;
}
break;
}
@@ -297,19 +308,6 @@ static int cursorThread(void * unused)
state.cursorVisible =
msg.udata & CURSOR_FLAG_VISIBLE;
if (msg.udata & CURSOR_FLAG_POSITION)
{
state.cursor.x = cursor->x;
state.cursor.y = cursor->y;
state.haveCursorPos = true;
if (!state.haveAligned && state.haveSrcSize && state.haveCurLocal)
{
alignMouseWithHost();
state.haveAligned = true;
}
}
if (msg.udata & CURSOR_FLAG_SHAPE)
{
switch(cursor->type)
@@ -323,6 +321,9 @@ static int cursorThread(void * unused)
continue;
}
state.cursor.hx = cursor->hx;
state.cursor.hy = cursor->hy;
const uint8_t * data = (const uint8_t *)(cursor + 1);
if (!state.lgr->on_mouse_shape(
state.lgrData,
@@ -339,6 +340,16 @@ static int cursorThread(void * unused)
}
}
if (msg.udata & CURSOR_FLAG_POSITION)
{
state.cursor.x = cursor->x;
state.cursor.y = cursor->y;
state.haveCursorPos = true;
if (state.haveSrcSize && state.haveCurLocal && !state.serverMode)
alignMouseWithGuest();
}
lgmpClientMessageDone(queue);
state.updateCursor = false;
@@ -349,10 +360,12 @@ static int cursorThread(void * unused)
state.cursor.x,
state.cursor.y
);
if (params.mouseRedraw)
lgSignalEvent(e_frame);
}
lgmpClientUnsubscribe(&queue);
state.running = false;
return 0;
}
@@ -363,11 +376,11 @@ static int frameThread(void * unused)
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
lgWaitEvent(e_startup, TIMEOUT_INFINITE);
if (!state.running)
if (state.state != APP_STATE_RUNNING)
return 0;
// subscribe to the frame queue
while(state.running)
while(state.state == APP_STATE_RUNNING)
{
status = lgmpClientSubscribe(state.lgmp, LGMP_Q_FRAME, &queue);
if (status == LGMP_OK)
@@ -380,11 +393,11 @@ static int frameThread(void * unused)
}
DEBUG_ERROR("lgmpClientSubscribe Failed: %s", lgmpStatusString(status));
state.running = false;
state.state = APP_STATE_SHUTDOWN;
break;
}
while(state.running)
while(state.state == APP_STATE_RUNNING)
{
LGMPMessage msg;
if ((status = lgmpClientProcess(queue, &msg)) != LGMP_OK)
@@ -395,7 +408,13 @@ static int frameThread(void * unused)
continue;
}
DEBUG_ERROR("lgmpClientProcess Failed: %s", lgmpStatusString(status));
if (status == LGMP_ERR_INVALID_SESSION)
state.state = APP_STATE_RESTART;
else
{
DEBUG_ERROR("lgmpClientProcess Failed: %s", lgmpStatusString(status));
state.state = APP_STATE_SHUTDOWN;
}
break;
}
@@ -435,6 +454,7 @@ static int frameThread(void * unused)
if (error)
{
lgmpClientMessageDone(queue);
state.state = APP_STATE_SHUTDOWN;
break;
}
@@ -453,6 +473,7 @@ static int frameThread(void * unused)
if (!state.lgr->on_frame_event(state.lgrData, lgrFormat, fb))
{
DEBUG_ERROR("renderer on frame event returned failure");
state.state = APP_STATE_SHUTDOWN;
break;
}
@@ -464,24 +485,23 @@ static int frameThread(void * unused)
}
lgmpClientUnsubscribe(&queue);
state.running = false;
return 0;
}
int spiceThread(void * arg)
{
while(state.running)
while(state.state != APP_STATE_SHUTDOWN)
if (!spice_process(1000))
{
if (state.running)
if (state.state != APP_STATE_SHUTDOWN)
{
state.running = false;
state.state = APP_STATE_SHUTDOWN;
DEBUG_ERROR("failed to process spice messages");
}
break;
}
state.running = false;
state.state = APP_STATE_SHUTDOWN;
return 0;
}
@@ -657,10 +677,22 @@ void spiceClipboardRequest(const SpiceDataType type)
state.lgc->request(spice_type_to_clipboard_type(type));
}
static void warpMouse(int x, int y)
{
if (state.warpState != WARP_STATE_ON)
return;
state.warpFromX = state.curLastX;
state.warpFromY = state.curLastY;
state.warpToX = x;
state.warpToY = y;
state.warpState = WARP_STATE_ACTIVE;
SDL_WarpMouseInWindow(state.window, x, y);
}
static void handleMouseMoveEvent(int ex, int ey)
{
static bool wrapping = false;
static int wrapX, wrapY;
state.curLocalX = ex;
state.curLocalY = ey;
@@ -669,28 +701,23 @@ static void handleMouseMoveEvent(int ex, int ey)
if (state.ignoreInput || !params.useSpiceInput)
return;
if (state.warpState == WARP_STATE_ACTIVE)
{
if (ex == state.warpToX && ey == state.warpToY)
{
state.curLastX += state.warpToX - state.warpFromX;
state.curLastY += state.warpToY - state.warpFromY;
state.warpState = WARP_STATE_ON;
}
}
if (state.serverMode)
{
if (wrapping)
if (
ex < 100 || ex > state.windowW - 100 ||
ey < 100 || ey > state.windowH - 100)
{
if (ex == state.windowW / 2 && ey == state.windowH / 2)
{
state.curLastX += (state.windowW / 2) - wrapX;
state.curLastY += (state.windowH / 2) - wrapY;
wrapping = false;
}
}
else
{
if (
ex < 100 || ex > state.windowW - 100 ||
ey < 100 || ey > state.windowH - 100)
{
wrapping = true;
wrapX = ex;
wrapY = ey;
SDL_WarpMouseInWindow(state.window, state.windowW / 2, state.windowH / 2);
}
warpMouse(state.windowW / 2, state.windowH / 2);
}
}
else
@@ -702,6 +729,10 @@ static void handleMouseMoveEvent(int ex, int ey)
{
state.cursorInView = false;
state.updateCursor = true;
state.warpState = WARP_STATE_OFF;
if (params.useSpiceInput)
state.drawCursor = false;
return;
}
}
@@ -710,6 +741,9 @@ static void handleMouseMoveEvent(int ex, int ey)
{
state.cursorInView = true;
state.updateCursor = true;
state.drawCursor = true;
if (state.warpState == WARP_STATE_ARMED)
state.warpState = WARP_STATE_ON;
}
int rx = ex - state.curLastX;
@@ -749,9 +783,9 @@ static void alignMouseWithGuest()
if (state.ignoreInput || !params.useSpiceInput)
return;
state.curLastX = (int)round((float)state.cursor.x / state.scaleX) + state.dstRect.x;
state.curLastY = (int)round((float)state.cursor.y / state.scaleY) + state.dstRect.y;
SDL_WarpMouseInWindow(state.window, state.curLastX, state.curLastY);
state.curLastX = (int)round((float)(state.cursor.x + state.cursor.hx) / state.scaleX) + state.dstRect.x;
state.curLastY = (int)round((float)(state.cursor.y + state.cursor.hy) / state.scaleY) + state.dstRect.y;
warpMouse(state.curLastX, state.curLastY);
}
static void alignMouseWithHost()
@@ -762,8 +796,8 @@ static void alignMouseWithHost()
if (!state.haveCursorPos || state.serverMode)
return;
state.curLastX = (int)round((float)state.cursor.x / state.scaleX) + state.dstRect.x;
state.curLastY = (int)round((float)state.cursor.y / state.scaleY) + state.dstRect.y;
state.curLastX = (int)round((float)(state.cursor.x + state.cursor.hx) / state.scaleX) + state.dstRect.x;
state.curLastY = (int)round((float)(state.cursor.y + state.cursor.hy) / state.scaleY) + state.dstRect.y;
handleMouseMoveEvent(state.curLocalX, state.curLocalY);
}
@@ -785,6 +819,7 @@ static void handleWindowLeave()
state.drawCursor = false;
state.cursorInView = false;
state.updateCursor = true;
state.warpState = WARP_STATE_OFF;
}
static void handleWindowEnter()
@@ -795,6 +830,7 @@ static void handleWindowEnter()
alignMouseWithHost();
state.drawCursor = true;
state.updateCursor = true;
state.warpState = WARP_STATE_ARMED;
}
int eventFilter(void * userdata, SDL_Event * event)
@@ -806,7 +842,7 @@ int eventFilter(void * userdata, SDL_Event * event)
if (!params.ignoreQuit)
{
DEBUG_INFO("Quit event received, exiting...");
state.running = false;
state.state = APP_STATE_SHUTDOWN;
}
return 0;
}
@@ -833,7 +869,7 @@ int eventFilter(void * userdata, SDL_Event * event)
// allow a window close event to close the application even if ignoreQuit is set
case SDL_WINDOWEVENT_CLOSE:
state.running = false;
state.state = APP_STATE_SHUTDOWN;
break;
}
return 0;
@@ -937,7 +973,9 @@ int eventFilter(void * userdata, SDL_Event * event)
state.serverMode ? "Capture Enabled" : "Capture Disabled"
);
if (!state.serverMode)
if (state.serverMode)
state.warpState = WARP_STATE_ON;
else
alignMouseWithGuest();
}
}
@@ -1033,7 +1071,7 @@ void int_handler(int signal)
case SIGINT:
case SIGTERM:
DEBUG_INFO("Caught signal, shutting down...");
state.running = false;
state.state = APP_STATE_SHUTDOWN;
break;
}
}
@@ -1081,7 +1119,7 @@ static void toggle_input(SDL_Scancode key, void * opaque)
static void quit(SDL_Scancode key, void * opaque)
{
state.running = false;
state.state = APP_STATE_SHUTDOWN;
}
static void mouse_sens_inc(SDL_Scancode key, void * opaque)
@@ -1164,7 +1202,7 @@ static void release_key_binds()
static int lg_run()
{
memset(&state, 0, sizeof(state));
state.running = true;
state.state = APP_STATE_RUNNING;
state.scaleX = 1.0f;
state.scaleY = 1.0f;
state.resizeDone = true;
@@ -1227,10 +1265,10 @@ static int lg_run()
return -1;
}
while(state.running && !spice_ready())
while(state.state != APP_STATE_SHUTDOWN && !spice_ready())
if (!spice_process(1000))
{
state.running = false;
state.state = APP_STATE_SHUTDOWN;
DEBUG_ERROR("Failed to process spice messages");
return -1;
}
@@ -1320,13 +1358,15 @@ static int lg_run()
// ensure renderer viewport is aware of the current window size
updatePositionInfo();
// use a default of 60FPS now that frame updates are host update triggered
if (params.fpsMin == -1)
state.frameTime = 1e9 / 60;
{
// minimum 60fps to keep interactivity decent
state.frameTime = 1000000000ULL / 60ULL;
}
else
{
DEBUG_INFO("Using the FPS minimum from args: %d", params.fpsMin);
state.frameTime = 1e9 / params.fpsMin;
state.frameTime = 1000000000ULL / (unsigned long long)params.fpsMin;
}
register_key_binds();
@@ -1442,7 +1482,7 @@ static int lg_run()
LGMP_STATUS status;
while(state.running)
while(state.state == APP_STATE_RUNNING)
{
if ((status = lgmpClientInit(state.shm.mem, state.shm.size, &state.lgmp)) == LGMP_OK)
break;
@@ -1457,9 +1497,10 @@ static int lg_run()
uint32_t udataSize;
KVMFR *udata;
int waitCount = 0;
while(state.running)
restart:
while(state.state == APP_STATE_RUNNING)
{
if ((status = lgmpClientSessionInit(state.lgmp, &udataSize, (uint8_t **)&udata)) == LGMP_OK)
break;
@@ -1488,9 +1529,12 @@ static int lg_run()
SDL_WaitEventTimeout(NULL, 1000);
}
if (!state.running)
if (state.state != APP_STATE_RUNNING)
return -1;
// dont show warnings again after the first startup
waitCount = 100;
const bool magicMatches = memcmp(udata->magic, KVMFR_MAGIC, sizeof(udata->magic)) == 0;
if (udataSize != sizeof(KVMFR) || !magicMatches || udata->version != KVMFR_VERSION)
{
@@ -1527,14 +1571,30 @@ static int lg_run()
return -1;
}
while(state.running)
while(state.state == APP_STATE_RUNNING)
{
if (!lgmpClientSessionValid(state.lgmp))
{
DEBUG_WARN("Session is invalid, has the host shutdown?");
state.state = APP_STATE_RESTART;
break;
}
SDL_WaitEventTimeout(NULL, 1000);
SDL_WaitEventTimeout(NULL, 100);
}
if (state.state == APP_STATE_RESTART)
{
lgSignalEvent(e_startup);
lgSignalEvent(e_frame);
lgJoinThread(t_frame , NULL);
lgJoinThread(t_cursor, NULL);
t_frame = NULL;
t_cursor = NULL;
state.state = APP_STATE_RUNNING;
state.lgr->on_restart(state.lgrData);
DEBUG_INFO("Waiting for the host to restart...");
goto restart;
}
return 0;
@@ -1542,8 +1602,7 @@ static int lg_run()
static void lg_shutdown()
{
state.running = false;
state.state = APP_STATE_SHUTDOWN;
if (t_render)
{
lgSignalEvent(e_startup);

View File

@@ -29,9 +29,30 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "spice/spice.h"
#include <lgmp/client.h>
enum RunState
{
APP_STATE_RUNNING,
APP_STATE_RESTART,
APP_STATE_SHUTDOWN
};
struct CursorInfo
{
int x , y;
int hx, hy;
};
enum WarpState
{
WARP_STATE_ARMED,
WARP_STATE_ON,
WARP_STATE_ACTIVE,
WARP_STATE_OFF
};
struct AppState
{
bool running;
enum RunState state;
bool ignoreInput;
bool escapeActive;
SDL_Scancode escapeAction;
@@ -42,7 +63,7 @@ struct AppState
int windowW, windowH;
SDL_Point srcSize;
LG_RendererRect dstRect;
SDL_Point cursor;
struct CursorInfo cursor;
bool cursorVisible;
bool serverMode;
@@ -60,6 +81,10 @@ struct AppState
int curLocalY;
bool haveAligned;
enum WarpState warpState;
int warpFromX, warpFromY;
int warpToX , warpToY;
const LG_Renderer * lgr;
void * lgrData;
bool lgrResize;
@@ -135,6 +160,7 @@ struct AppParams
const char * windowTitle;
int mouseSens;
bool mouseRedraw;
};
struct CBRequest

View File

@@ -5,6 +5,8 @@ include_directories(
${PROJECT_SOURCE_DIR}/include
)
add_definitions(-D_GNU_SOURCE)
if(ENABLE_BACKTRACE)
add_definitions(-DENABLE_BACKTRACE)
endif()

View File

@@ -51,7 +51,7 @@ typedef enum CursorType
CursorType;
#define KVMFR_MAGIC "KVMFR---"
#define KVMFR_VERSION 2
#define KVMFR_VERSION 3
typedef struct KVMFR
{
@@ -65,6 +65,7 @@ typedef struct KVMFRCursor
{
int16_t x, y; // cursor x & y position
CursorType type; // shape buffer data type
int8_t hx, hy; // shape hotspot x & y
uint32_t width; // width of the shape
uint32_t height; // height of the shape
uint32_t pitch; // row length in bytes of the shape

View File

@@ -24,8 +24,10 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <stdatomic.h>
#include <emmintrin.h>
#include <smmintrin.h>
#include <unistd.h>
#define FB_CHUNK_SIZE 1048576
#define FB_CHUNK_SIZE 1048576 // 1MB
#define FB_SPIN_LIMIT 10000 // 10ms
struct stFrameBuffer
{
@@ -40,7 +42,6 @@ void framebuffer_wait(const FrameBuffer * frame, size_t size)
while(atomic_load_explicit(&frame->wp, memory_order_acquire) != size) {}
}
bool framebuffer_read(const FrameBuffer * frame, void * restrict dst,
size_t dstpitch, size_t height, size_t width, size_t bpp, size_t pitch)
{
@@ -54,11 +55,18 @@ bool framebuffer_read(const FrameBuffer * frame, void * restrict dst,
while(y < height)
{
uint_least32_t wp;
int spinCount = 0;
/* spinlock */
do
wp = atomic_load_explicit(&frame->wp, memory_order_acquire);
while(wp - rp < linewidth)
{
if (++spinCount == FB_SPIN_LIMIT)
return false;
usleep(1);
wp = atomic_load_explicit(&frame->wp, memory_order_acquire);
while(wp - rp < pitch);
}
_mm_mfence();
__m128i * restrict s = (__m128i *)(frame->data + rp);
@@ -104,11 +112,18 @@ bool framebuffer_read_fn(const FrameBuffer * frame, size_t height, size_t width,
while(y < height)
{
uint_least32_t wp;
int spinCount = 0;
/* spinlock */
do
wp = atomic_load_explicit(&frame->wp, memory_order_acquire);
while(wp - rp < linewidth)
{
if (++spinCount == FB_SPIN_LIMIT)
return false;
usleep(1);
wp = atomic_load_explicit(&frame->wp, memory_order_acquire);
while(wp - rp < pitch);
}
if (!fn(opaque, frame->data + rp, linewidth))
return false;

View File

@@ -17,7 +17,6 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#define _GNU_SOURCE
#include "common/crash.h"
#include "common/debug.h"

View File

@@ -83,19 +83,20 @@ bool lgWaitEventAbs(LGEvent * handle, struct timespec * ts)
}
bool ret = true;
int res;
while(ret && !atomic_load(&handle->flag))
{
if (!ts)
{
if (pthread_cond_wait(&handle->cond, &handle->mutex) != 0)
if ((res = pthread_cond_wait(&handle->cond, &handle->mutex)) != 0)
{
DEBUG_ERROR("Wait to wait on the condition");
DEBUG_ERROR("Failed to wait on the condition (err: %d)", res);
ret = false;
}
}
else
{
switch(pthread_cond_timedwait(&handle->cond, &handle->mutex, ts))
switch((res = pthread_cond_timedwait(&handle->cond, &handle->mutex, ts)))
{
case 0:
break;
@@ -106,7 +107,7 @@ bool lgWaitEventAbs(LGEvent * handle, struct timespec * ts)
default:
ret = false;
DEBUG_ERROR("Timed wait failed");
DEBUG_ERROR("Timed wait failed (err: %d)", res);
break;
}
}
@@ -145,6 +146,9 @@ bool lgWaitEventNS(LGEvent * handle, unsigned int timeout)
bool lgWaitEvent(LGEvent * handle, unsigned int timeout)
{
if (timeout == TIMEOUT_INFINITE)
return lgWaitEventAbs(handle, NULL);
return lgWaitEventNS(handle, timeout * 1000000);
}

View File

@@ -54,6 +54,8 @@ bool lgCreateThread(const char * name, LGThreadFunction function, void * opaque,
*handle = NULL;
return false;
}
pthread_setname_np((*handle)->handle, name);
return true;
}
@@ -71,4 +73,4 @@ bool lgJoinThread(LGThread * handle, int * resultCode)
free(handle);
return true;
}
}

View File

@@ -20,8 +20,8 @@ Currently only Windows is supported however there is some initial support for Li
6. configure the project and build it
```
mkdir LookingGlass/c-host/build
cd LookingGlass/c-host/build
mkdir LookingGlass/host/build
cd LookingGlass/host/build
cmake -G "MSYS Makefiles" ..
make
```
@@ -44,17 +44,46 @@ cmake -DCMAKE_TOOLCHAIN_FILE=../toolchain-mingw64.cmake ..
make
```
## Building the Windows installer
Install NSIS compiler
Build the host program, see above sections.
Build installer with `makensis platform/Windows/installer.nsi`
The resulting installer will be at
`platform/Windows/looking-glass-host-setup.exe`
## Where is the log?
It is in your user's temp directory:
%TEMP%\looking-glass-host.txt
For example:
Or if running as a system service it will be located in:
C:\Users\YourUser\AppData\Local\Temp\looking-glass-host.txt
C:\Windows\Temp\looking-glass-host.txt
You can also open it by simply right clicking the tray icon and selecting "Open Log File"
You can find out where the file is by right clicking on the tray icon and
selecting "Log File Location"
### High priority capture using DXGI and Secure Desktop (UAC) capture support
By default Windows gives priority to the foreground application for any GPU
work which causes issues with capture if the foreground application is consuming
100% of the available GPU resources. The looking glass host application is able
to increase the kernel GPU thread to realtime priority which fixes this, but in
order to do so it must run as the `SYSTEM` user account. To do this, Looking
Glass needs to run as a service. This can be accomplished by either using the
NSIS installer which will do this for you, or you can use the following command
to Install the service manually:
looking-glass-host.exe InstallService
To remove the service use the following command:
looking-glass-host.exe UninstallService
This will also enable the host application to capture the secure desktop which
includes things like the lock screen and UAC prompts.
## Why does this version require Administrator privileges

View File

@@ -67,6 +67,7 @@ typedef struct CapturePointer
bool shapeUpdate;
CaptureFormat format;
unsigned int hx, hy;
unsigned int width, height;
unsigned int pitch;
}

View File

@@ -7,6 +7,7 @@ include_directories(
add_library(platform_Windows STATIC
src/platform.c
src/service.c
src/mousehook.c
)
@@ -23,9 +24,19 @@ target_link_libraries(platform_Windows
"${PROJECT_BINARY_DIR}/resource.o"
lg_common
capture
userenv
wtsapi32
psapi
)
target_include_directories(platform_Windows
PRIVATE
src
)
# these are for the nsis installer generator
configure_file("${PROJECT_SOURCE_DIR}/installer.nsi" "${PROJECT_BINARY_DIR}/installer.nsi" COPYONLY)
configure_file("${PROJECT_TOP}/resources/icon.ico" "${PROJECT_BINARY_DIR}/icon.ico" COPYONLY)
configure_file("${PROJECT_TOP}/VERSION" "${PROJECT_BINARY_DIR}/VERSION" COPYONLY)
configure_file("${PROJECT_TOP}/LICENSE" "${PROJECT_BINARY_DIR}/LICENSE.txt" COPYONLY)

View File

@@ -26,6 +26,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "common/event.h"
#include <assert.h>
#include <stdatomic.h>
#include <unistd.h>
#include <dxgi.h>
#include <d3d11.h>
#include <d3dcommon.h>
@@ -55,7 +57,7 @@ enum TextureState
typedef struct Texture
{
enum TextureState state;
volatile enum TextureState state;
ID3D11Texture2D * tex;
D3D11_MAPPED_SUBRESOURCE map;
}
@@ -82,7 +84,7 @@ struct iface
Texture * texture;
int texRIndex;
int texWIndex;
volatile int texReady;
atomic_int texReady;
bool needsRelease;
CaptureGetPointerBuffer getPointerBufferFn;
@@ -202,8 +204,8 @@ static bool dxgi_init()
{
DEBUG_INFO("The above error(s) will prevent LG from being able to capture the secure desktop (UAC dialogs)");
DEBUG_INFO("This is not a failure, please do not report this as an issue.");
DEBUG_INFO("To fix this run LG using the PsExec SysInternals tool from Microsoft.");
DEBUG_INFO("https://docs.microsoft.com/en-us/sysinternals/downloads/psexec");
DEBUG_INFO("To fix this, install and run the Looking Glass host as a service.");
DEBUG_INFO("looking-glass-host.exe InstallService");
}
// this is required for DXGI 1.5 support to function
@@ -228,9 +230,9 @@ static bool dxgi_init()
this->stop = false;
this->texRIndex = 0;
this->texWIndex = 0;
this->texReady = 0;
atomic_store(&this->texReady, 0);
lgResetEvent(this->frameEvent );
lgResetEvent(this->frameEvent);
status = CreateDXGIFactory1(&IID_IDXGIFactory1, (void **)&this->factory);
if (FAILED(status))
@@ -408,9 +410,10 @@ static bool dxgi_init()
status = fn(GetCurrentProcess(), D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME);
if (FAILED(status))
{
DEBUG_INFO("Failed to set realtime GPU priority, this is not an error!");
DEBUG_INFO("To fix this run LG using the PsExec SysInternals tool from Microsoft.");
DEBUG_INFO("https://docs.microsoft.com/en-us/sysinternals/downloads/psexec");
DEBUG_WARN("Failed to set realtime GPU priority.");
DEBUG_INFO("This is not a failure, please do not report this as an issue.");
DEBUG_INFO("To fix this, install and run the Looking Glass host as a service.");
DEBUG_INFO("looking-glass-host.exe InstallService");
}
}
}
@@ -704,6 +707,15 @@ static CaptureResult dxgi_capture()
DXGI_OUTDUPL_FRAME_INFO frameInfo;
IDXGIResource * res;
bool copyFrame = false;
bool copyPointer = false;
ID3D11Texture2D * src;
bool postPointer = false;
CapturePointer pointer = { 0 };
void * pointerShape = NULL;
UINT pointerShapeSize = 0;
// release the prior frame
result = dxgi_releaseFrame();
if (result != CAPTURE_RESULT_OK)
@@ -735,7 +747,7 @@ static CaptureResult dxgi_capture()
// check if the texture is free, if not skip the frame to keep up
if (tex->state == TEXTURE_STATE_UNUSED)
{
ID3D11Texture2D * src;
copyFrame = true;
status = IDXGIResource_QueryInterface(res, &IID_ID3D11Texture2D, (void **)&src);
if (FAILED(status))
{
@@ -743,19 +755,51 @@ static CaptureResult dxgi_capture()
IDXGIResource_Release(res);
return CAPTURE_RESULT_ERROR;
}
}
}
LOCKED({
// issue the copy from GPU to CPU RAM and release the src
IDXGIResource_Release(res);
// if the pointer shape has changed
uint32_t bufferSize;
if (frameInfo.PointerShapeBufferSize > 0)
{
if(!this->getPointerBufferFn(&pointerShape, &bufferSize))
DEBUG_WARN("Failed to obtain a buffer for the pointer shape");
else
copyPointer = true;
}
if (copyFrame || copyPointer)
{
DXGI_OUTDUPL_POINTER_SHAPE_INFO shapeInfo;
LOCKED(
{
if (copyFrame)
{
// issue the copy from GPU to CPU RAM
ID3D11DeviceContext_CopyResource(this->deviceContext,
(ID3D11Resource *)tex->tex, (ID3D11Resource *)src);
});
}
if (copyPointer)
{
// grab the pointer shape
status = IDXGIOutputDuplication_GetFramePointerShape(
this->dup, bufferSize, pointerShape, &pointerShapeSize, &shapeInfo);
}
ID3D11DeviceContext_Flush(this->deviceContext);
});
if (copyFrame)
{
ID3D11Texture2D_Release(src);
// set the state, and signal
tex->state = TEXTURE_STATE_PENDING_MAP;
INTERLOCKED_INC(&this->texReady);
lgSignalEvent(this->frameEvent);
if (atomic_fetch_add_explicit(&this->texReady, 1, memory_order_relaxed) == 0)
lgSignalEvent(this->frameEvent);
// advance the write index
if (++this->texWIndex == this->maxTextures)
@@ -764,16 +808,63 @@ static CaptureResult dxgi_capture()
// update the last frame time
this->frameTime.QuadPart = frameInfo.LastPresentTime.QuadPart;
}
if (copyPointer)
{
result = dxgi_hResultToCaptureResult(status);
if (result != CAPTURE_RESULT_OK)
{
if (result == CAPTURE_RESULT_ERROR)
DEBUG_WINERROR("Failed to get the new pointer shape", status);
return result;
}
switch(shapeInfo.Type)
{
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR : pointer.format = CAPTURE_FMT_COLOR ; break;
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: pointer.format = CAPTURE_FMT_MASKED; break;
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME : pointer.format = CAPTURE_FMT_MONO ; break;
default:
DEBUG_ERROR("Unsupported cursor format");
return CAPTURE_RESULT_ERROR;
}
CURSORINFO ci = { .cbSize = sizeof(CURSORINFO) };
if (!GetCursorInfo(&ci))
{
DEBUG_WINERROR("GetCursorInfo failed", GetLastError());
return CAPTURE_RESULT_ERROR;
}
if (ci.hCursor)
{
ICONINFO ii;
if (!GetIconInfo(ci.hCursor, &ii))
{
DEBUG_WINERROR("GetIconInfo failed", GetLastError());
return CAPTURE_RESULT_ERROR;
}
DeleteObject(ii.hbmMask);
DeleteObject(ii.hbmColor);
pointer.hx = ii.xHotspot;
pointer.hy = ii.yHotspot;
}
else
{
pointer.hx = 0;
pointer.hy = 0;
}
pointer.shapeUpdate = true;
pointer.width = shapeInfo.Width;
pointer.height = shapeInfo.Height;
pointer.pitch = shapeInfo.Pitch;
postPointer = true;
}
}
IDXGIResource_Release(res);
// if the pointer has moved or changed state
bool postPointer = false;
CapturePointer pointer = { 0 };
void * pointerShape = NULL;
UINT pointerShapeSize = 0;
if (frameInfo.LastMouseUpdateTime.QuadPart)
{
/* the pointer position is only valid if the pointer is visible */
@@ -798,43 +889,6 @@ static CaptureResult dxgi_capture()
}
}
// if the pointer shape has changed
if (frameInfo.PointerShapeBufferSize > 0)
{
uint32_t bufferSize;
if(!this->getPointerBufferFn(&pointerShape, &bufferSize))
DEBUG_WARN("Failed to obtain a buffer for the pointer shape");
else
{
DXGI_OUTDUPL_POINTER_SHAPE_INFO shapeInfo;
LOCKED({status = IDXGIOutputDuplication_GetFramePointerShape(this->dup, bufferSize, pointerShape, &pointerShapeSize, &shapeInfo);});
result = dxgi_hResultToCaptureResult(status);
if (result != CAPTURE_RESULT_OK)
{
if (result == CAPTURE_RESULT_ERROR)
DEBUG_WINERROR("Failed to get the new pointer shape", status);
return result;
}
switch(shapeInfo.Type)
{
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR : pointer.format = CAPTURE_FMT_COLOR ; break;
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: pointer.format = CAPTURE_FMT_MASKED; break;
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME : pointer.format = CAPTURE_FMT_MONO ; break;
default:
DEBUG_ERROR("Unsupported cursor format");
return CAPTURE_RESULT_ERROR;
}
pointer.shapeUpdate = true;
pointer.width = shapeInfo.Width;
pointer.height = shapeInfo.Height;
pointer.pitch = shapeInfo.Pitch;
postPointer = true;
}
}
// post back the pointer information
if (postPointer)
{
@@ -851,27 +905,39 @@ static CaptureResult dxgi_waitFrame(CaptureFrame * frame)
assert(this->initialized);
// NOTE: the event may be signaled when there are no frames available
if(this->texReady == 0)
if(atomic_load_explicit(&this->texReady, memory_order_acquire) == 0)
{
if (!lgWaitEvent(this->frameEvent, 1000))
return CAPTURE_RESULT_TIMEOUT;
if (this->texReady == 0)
// the count will still be zero if we are stopping
if(atomic_load_explicit(&this->texReady, memory_order_acquire) == 0)
return CAPTURE_RESULT_TIMEOUT;
}
Texture * tex = &this->texture[this->texRIndex];
// try to map the resource, but don't wait for it
HRESULT status;
LOCKED({status = ID3D11DeviceContext_Map(this->deviceContext, (ID3D11Resource*)tex->tex, 0, D3D11_MAP_READ, 0x100000L, &tex->map);});
if (status == DXGI_ERROR_WAS_STILL_DRAWING)
return CAPTURE_RESULT_TIMEOUT;
if (FAILED(status))
for (int i = 0; ; ++i)
{
DEBUG_WINERROR("Failed to map the texture", status);
return CAPTURE_RESULT_ERROR;
HRESULT status;
LOCKED({status = ID3D11DeviceContext_Map(this->deviceContext, (ID3D11Resource*)tex->tex, 0, D3D11_MAP_READ, 0x100000L, &tex->map);});
if (status == DXGI_ERROR_WAS_STILL_DRAWING)
{
if (i == 100)
return CAPTURE_RESULT_TIMEOUT;
usleep(1);
continue;
}
if (FAILED(status))
{
DEBUG_WINERROR("Failed to map the texture", status);
return CAPTURE_RESULT_ERROR;
}
break;
}
tex->state = TEXTURE_STATE_MAPPED;
@@ -882,7 +948,7 @@ static CaptureResult dxgi_waitFrame(CaptureFrame * frame)
frame->stride = this->stride;
frame->format = this->format;
INTERLOCKED_DEC(&this->texReady);
atomic_fetch_sub_explicit(&this->texReady, 1, memory_order_release);
return CAPTURE_RESULT_OK;
}

View File

@@ -352,8 +352,8 @@ static int pointerThread(void * unused)
}
this->mouseVisible = pointer.visible;
this->mouseHotX = pointer.x;
this->mouseHotY = pointer.y;
this->mouseHotX = pointer.hx;
this->mouseHotY = pointer.hy;
}
if (events[0])

View File

@@ -288,8 +288,8 @@ CaptureResult NvFBCToSysGetCursor(NvFBCHandle handle, CapturePointer * pointer,
return CAPTURE_RESULT_ERROR;
}
pointer->x = params.dwXHotSpot;
pointer->y = params.dwYHotSpot;
pointer->hx = params.dwXHotSpot;
pointer->hy = params.dwYHotSpot;
pointer->width = params.dwWidth;
pointer->height = params.dwHeight;
pointer->pitch = params.dwPitch;
@@ -327,4 +327,4 @@ CaptureResult NvFBCToSysGetCursor(NvFBCHandle handle, CapturePointer * pointer,
memcpy(buffer, params.pBits, params.dwBufferSize);
return CAPTURE_RESULT_OK;
}
}

View File

@@ -0,0 +1,198 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
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
!include "MUI2.nsh"
!include "FileFunc.nsh"
!include "LogicLib.nsh"
!include "Sections.nsh"
;Settings
Name "Looking Glass (host)"
OutFile "looking-glass-host-setup.exe"
Unicode true
RequestExecutionLevel admin
ShowInstDetails "show"
ShowUninstDetails "show"
InstallDir "$PROGRAMFILES64\Looking Glass (host)"
!define MUI_ICON "icon.ico"
!define MUI_UNICON "icon.ico"
!define MUI_LICENSEPAGE_BUTTON "Agree"
!define /file VERSION "VERSION"
;Install and uninstall pages
!insertmacro MUI_PAGE_LICENSE "LICENSE.txt"
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_LANGUAGE "English"
Function ShowHelpMessage
!define line1 "Command line options:$\r$\n$\r$\n"
!define line2 "/S - silent install (must be uppercase)$\r$\n"
!define line3 "/D=path\to\install\folder - Change install directory$\r$\n"
!define line4 " (Must be uppercase, the last option given and no quotes)$\r$\n$\r$\n"
!define line5 "/startmenu - create start menu shortcut$\r$\n"
!define line6 "/desktop - create desktop shortcut$\r$\n"
!define line7 "/noservice - do not create a service to auto start and elevate the host"
MessageBox MB_OK "${line1}${line2}${line3}${line4}${line5}${line6}${line7}"
Abort
FunctionEnd
Function .onInit
var /GLOBAL cmdLineParams
Push $R0
${GetParameters} $cmdLineParams
ClearErrors
${GetOptions} $cmdLineParams '/?' $R0
IfErrors +2 0
Call ShowHelpMessage
${GetOptions} $cmdLineParams '/H' $R0
IfErrors +2 0
Call ShowHelpMessage
Pop $R0
Var /GLOBAL option_startMenu
Var /GLOBAL option_desktop
Var /GlOBAL option_noservice
StrCpy $option_startMenu 0
StrCpy $option_desktop 0
StrCpy $option_noservice 0
Push $R0
${GetOptions} $cmdLineParams '/startmenu' $R0
IfErrors +2 0
StrCpy $option_startMenu 1
${GetOptions} $cmdLineParams '/desktop' $R0
IfErrors +2 0
StrCpy $option_desktop 1
${GetOptions} $cmdLineParams '/noservice' $R0
IfErrors +2 0
StrCpy $option_noservice 1
Pop $R0
FunctionEnd
;Install
Section "-Install" Section1
nsExec::Exec 'net.exe STOP "Looking Glass (host)"'
SetOutPath $INSTDIR
File ..\..\looking-glass-host.exe
File LICENSE.txt
WriteUninstaller $INSTDIR\uninstaller.exe
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
IntFmt $0 "0x%08X" $0
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)" \
"EstimatedSize" "$0"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)" \
"DisplayName" "Looking Glass (host)"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)" \
"UninstallString" "$\"$INSTDIR\uninstaller.exe$\""
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)" \
"QuietUninstallString" "$\"$INSTDIR\uninstaller.exe$\" /S"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)" \
"InstallLocation" "$INSTDIR"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)" \
"Publisher" "Geoffrey McRae"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)" \
"DisplayIcon" "$\"$INSTDIR\looking-glass-host.exe$\""
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)" \
"NoRepair" "1"
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)" \
"NoModify" "1"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)" \
"DisplayVersion" ${VERSION}
SectionEnd
Section "Looking Glass (host) Service" Section2
${If} $option_noservice == 0
nsExec::Exec '"$INSTDIR\looking-glass-host.exe" UninstallService'
nsExec::Exec '"$INSTDIR\looking-glass-host.exe" InstallService'
${EndIf}
SectionEnd
Section /o "Desktop Shortcut" Section3
StrCpy $option_desktop 1
SectionEnd
Section /o "Start Menu Shortcut" Section4
StrCpy $option_startMenu 1
SectionEnd
Section "-Hidden Start Menu" Section5
SetShellVarContext all
${If} $option_startMenu == 1
CreateShortCut "$SMPROGRAMS\Looking Glass (host).lnk" $INSTDIR\looking-glass-host.exe
${EndIf}
${If} $option_desktop == 1
CreateShortCut "$DESKTOP\Looking Glass (host).lnk" $INSTDIR\looking-glass-host.exe
${EndIf}
SectionEnd
Section "Uninstall" Section6
SetShellVarContext all
nsExec::Exec 'net.exe STOP "Looking Glass (host)"'
nsExec::Exec '"$INSTDIR\looking-glass-host.exe" UninstallService'
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)"
Delete "$SMPROGRAMS\Looking Glass (host).lnk"
Delete "$DESKTOP\Looking Glass (host).lnk"
Delete "$INSTDIR\uninstaller.exe"
Delete "$INSTDIR\looking-glass-host.exe"
Delete "$INSTDIR\LICENSE.txt"
RMDir $INSTDIR
SectionEnd
;Description text for selection of install items
LangString DESC_Section1 ${LANG_ENGLISH} "Install Files into $INSTDIR"
LangString DESC_Section2 ${LANG_ENGLISH} "Install service to automatically start Looking Glass (host)."
LangString DESC_Section3 ${LANG_ENGLISH} "Create desktop shortcut icon."
LangString DESC_Section4 ${LANG_ENGLISH} "Create start menu shortcut."
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
!insertmacro MUI_DESCRIPTION_TEXT ${Section1} $(DESC_Section1)
!insertmacro MUI_DESCRIPTION_TEXT ${Section2} $(DESC_Section2)
!insertmacro MUI_DESCRIPTION_TEXT ${Section3} $(DESC_Section3)
!insertmacro MUI_DESCRIPTION_TEXT ${Section4} $(DESC_Section4)
!insertmacro MUI_FUNCTION_DESCRIPTION_END

View File

@@ -18,6 +18,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "platform.h"
#include "service.h"
#include "windows/mousehook.h"
#include <windows.h>
@@ -31,7 +32,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "common/locking.h"
#include "common/thread.h"
#define ID_MENU_OPEN_LOG 3000
#define ID_MENU_SHOW_LOG 3000
#define ID_MENU_EXIT 3001
struct AppState
@@ -42,9 +43,11 @@ struct AppState
int argc;
char ** argv;
char executable[MAX_PATH + 1];
HWND messageWnd;
HMENU trayMenu;
char executable[MAX_PATH + 1];
HWND messageWnd;
NOTIFYICONDATA iconData;
UINT trayRestartMsg;
HMENU trayMenu;
};
static struct AppState app = {0};
@@ -54,6 +57,30 @@ HWND MessageHWND;
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)
#define MSGFLT_ALLOW (1)
#define MSGFLT_DISALLOW (2)
#endif
typedef WINBOOL WINAPI (*PChangeWindowMessageFilterEx)(HWND hwnd, UINT message, DWORD action, void * pChangeFilterStruct);
PChangeWindowMessageFilterEx _ChangeWindowMessageFilterEx = NULL;
static void RegisterTrayIcon()
{
// register our TrayIcon
if (!app.iconData.cbSize)
{
app.iconData.cbSize = sizeof(NOTIFYICONDATA);
app.iconData.hWnd = app.messageWnd;
app.iconData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
app.iconData.uCallbackMessage = WM_TRAYICON;
strncpy(app.iconData.szTip, "Looking Glass (host)", sizeof(app.iconData.szTip));
app.iconData.hIcon = LoadIcon(app.hInst, IDI_APPLICATION);
}
Shell_NotifyIcon(NIM_ADD, &app.iconData);
}
LRESULT CALLBACK DummyWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
@@ -86,41 +113,39 @@ LRESULT CALLBACK DummyWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
);
if (clicked == ID_MENU_EXIT ) app_quit();
else if (clicked == ID_MENU_OPEN_LOG)
else if (clicked == ID_MENU_SHOW_LOG)
{
const char * logFile = option_get_string("os", "logFile");
if (strcmp(logFile, "stderr") == 0)
DEBUG_INFO("Ignoring request to open the logFile, logging to stderr");
else
ShellExecute(NULL, NULL, logFile, NULL, NULL, SW_SHOWNORMAL);
{
/* If LG is running as SYSTEM, ShellExecute would launch a process
* as the SYSTEM user also, for security we will just show the file
* location instead */
//ShellExecute(NULL, NULL, logFile, NULL, NULL, SW_SHOWNORMAL);
MessageBoxA(hwnd, logFile, "Log File Location", MB_OK | MB_ICONINFORMATION);
}
}
}
break;
}
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
if (msg == app.trayRestartMsg)
RegisterTrayIcon();
break;
}
return 0;
return DefWindowProc(hwnd, msg, wParam, lParam);
}
static int appThread(void * opaque)
{
// register our TrayIcon
NOTIFYICONDATA iconData =
{
.cbSize = sizeof(NOTIFYICONDATA),
.hWnd = app.messageWnd,
.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP,
.uCallbackMessage = WM_TRAYICON,
.szTip = "Looking Glass (host)"
};
iconData.hIcon = LoadIcon(app.hInst, IDI_APPLICATION);
Shell_NotifyIcon(NIM_ADD, &iconData);
RegisterTrayIcon();
int result = app_main(app.argc, app.argv);
Shell_NotifyIcon(NIM_DELETE, &iconData);
Shell_NotifyIcon(NIM_DELETE, &app.iconData);
mouseHook_remove();
SendMessage(app.messageWnd, WM_DESTROY, 0, 0);
return result;
@@ -144,6 +169,21 @@ static BOOL WINAPI CtrlHandler(DWORD dwCtrlType)
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
// convert the command line to the standard argc and argv
LPWSTR * wargv = CommandLineToArgvW(GetCommandLineW(), &app.argc);
app.argv = malloc(sizeof(char *) * app.argc);
for(int i = 0; i < app.argc; ++i)
{
const size_t s = (wcslen(wargv[i])+1) * 2;
app.argv[i] = malloc(s);
wcstombs(app.argv[i], wargv[i], s);
}
LocalFree(wargv);
GetModuleFileName(NULL, app.executable, sizeof(app.executable));
if (HandleService(app.argc, app.argv))
return 0;
/* this is a bit of a hack but without this --help will produce no output in a windows command prompt */
if (!IsDebuggerPresent() && AttachConsole(ATTACH_PARENT_PROCESS))
{
@@ -183,19 +223,6 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
option_register(options);
free(logFilePath);
// convert the command line to the standard argc and argv
LPWSTR * wargv = CommandLineToArgvW(GetCommandLineW(), &app.argc);
app.argv = malloc(sizeof(char *) * app.argc);
for(int i = 0; i < app.argc; ++i)
{
const size_t s = (wcslen(wargv[i])+1) * 2;
app.argv[i] = malloc(s);
wcstombs(app.argv[i], wargv[i], s);
}
LocalFree(wargv);
GetModuleFileName(NULL, app.executable, sizeof(app.executable));
// setup a handler for ctrl+c
SetConsoleCtrlHandler(CtrlHandler, TRUE);
@@ -209,21 +236,31 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
wx.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
wx.hCursor = LoadCursor(NULL, IDC_ARROW);
wx.hbrBackground = (HBRUSH)COLOR_APPWORKSPACE;
if (!RegisterClassEx(&wx))
ATOM class;
if (!(class = RegisterClassEx(&wx)))
{
DEBUG_ERROR("Failed to register message window class");
result = -1;
goto finish;
}
app.messageWnd = CreateWindowEx(0, "DUMMY_CLASS", "DUMMY_NAME", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
app.trayRestartMsg = RegisterWindowMessage("TaskbarCreated");
app.messageWnd = CreateWindowEx(0, MAKEINTATOM(class), NULL, 0, 0, 0, 0, 0, NULL, NULL, hInstance, NULL);
// this is needed so that unprivileged processes can send us this message
HMODULE user32 = GetModuleHandle("user32.dll");
_ChangeWindowMessageFilterEx = (PChangeWindowMessageFilterEx)GetProcAddress(user32, "ChangeWindowMessageFilterEx");
if (_ChangeWindowMessageFilterEx)
_ChangeWindowMessageFilterEx(app.messageWnd, app.trayRestartMsg, MSGFLT_ALLOW, NULL);
// set the global
MessageHWND = app.messageWnd;
app.trayMenu = CreatePopupMenu();
AppendMenu(app.trayMenu, MF_STRING , ID_MENU_OPEN_LOG, "Open Log File");
AppendMenu(app.trayMenu, MF_SEPARATOR, 0 , NULL );
AppendMenu(app.trayMenu, MF_STRING , ID_MENU_EXIT , "Exit" );
AppendMenu(app.trayMenu, MF_STRING , ID_MENU_SHOW_LOG, "Log File Location");
AppendMenu(app.trayMenu, MF_SEPARATOR, 0 , NULL );
AppendMenu(app.trayMenu, MF_STRING , ID_MENU_EXIT , "Exit" );
// create the application thread
LGThread * thread;

View File

@@ -0,0 +1,688 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
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
*/
#define INSTANCE_MUTEX_NAME "Global\\6f1a5eec-af3f-4a65-99dd-ebe0e4ecea55"
#include "interface/platform.h"
#include <stdio.h>
#include <stdbool.h>
#include <stdarg.h>
#include <stdlib.h>
#include <inttypes.h>
#include <windows.h>
#include <winsvc.h>
#include <psapi.h>
#include <sddl.h>
#include <userenv.h>
#include <wtsapi32.h>
#define SVCNAME "Looking Glass (host)"
#define SVC_ERROR ((DWORD)0xC0020001L)
struct Service
{
FILE * logFile;
bool running;
DWORD processId;
};
struct Service service = { 0 };
void doLog(const char * fmt, ...)
{
va_list args;
va_start(args, fmt);
vfprintf(service.logFile, fmt, args);
va_end(args);
}
static void setupLogging()
{
char tempPath[MAX_PATH+1];
GetTempPathA(sizeof(tempPath), tempPath);
int len = snprintf(NULL, 0, "%slooking-glass-host-service.txt", tempPath);
char * logFilePath = malloc(len + 1);
sprintf(logFilePath, "%slooking-glass-host-service.txt", tempPath);
service.logFile = fopen(logFilePath, "a+");
doLog("Startup\n");
}
static void finishLogging()
{
doLog("Finished\n");
fclose(service.logFile);
}
void winerr()
{
char buf[256];
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
buf, (sizeof(buf) / sizeof(char)), NULL);
doLog("0x%08lx - %s", GetLastError(), buf);
}
bool enablePriv(const char * name)
{
HANDLE hToken;
LUID luid;
TOKEN_PRIVILEGES tp = { 0 };
if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
{
doLog("failed to open the process\n");
winerr();
return -1;
}
if (!LookupPrivilegeValueA(NULL, name, &luid))
{
doLog("failed to lookup the privilege value\n");
winerr();
goto fail;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL,
NULL))
{
doLog("failed to adjust the token privilege\n");
winerr();
goto fail;
}
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
{
doLog("the token doesn't have the specified privilege - %s\n", name);
winerr();
goto fail;
}
return true;
fail:
CloseHandle(hToken);
return false;
}
HANDLE dupeSystemProcessToken()
{
DWORD count = 0;
DWORD returned;
do
{
count += 512;
DWORD pids[count];
EnumProcesses(pids, count * sizeof(DWORD), &returned);
}
while(returned / sizeof(DWORD) == count);
DWORD pids[count];
EnumProcesses(pids, count * sizeof(DWORD), &returned);
returned /= sizeof(DWORD);
for(DWORD i = 0; i < returned; ++i)
{
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pids[i]);
if (!hProcess)
continue;
HANDLE hToken;
if (!OpenProcessToken(hProcess,
TOKEN_QUERY | TOKEN_READ | TOKEN_IMPERSONATE | TOKEN_QUERY_SOURCE |
TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY | TOKEN_EXECUTE, &hToken))
goto err_proc;
DWORD tmp;
char userBuf[1024];
TOKEN_USER * user = (TOKEN_USER *)userBuf;
if (!GetTokenInformation(hToken, TokenUser, user, sizeof(userBuf), &tmp))
goto err_token;
CHAR * sid = NULL;
if (!ConvertSidToStringSidA(user->User.Sid, &sid))
goto err_token;
if (strcmp(sid, "S-1-5-18") == 0)
{
LocalFree(sid);
CloseHandle(hProcess);
// duplicate the token so we can use it
HANDLE hDupe = NULL;
if (!DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityImpersonation,
TokenPrimary, &hDupe))
hDupe = NULL;
CloseHandle(hToken);
return hDupe;
}
LocalFree(sid);
err_token:
CloseHandle(hToken);
err_proc:
CloseHandle(hProcess);
}
return NULL;
}
DWORD GetInteractiveSessionID()
{
PWTS_SESSION_INFO pSessionInfo;
DWORD count;
DWORD ret = 0;
if (!WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pSessionInfo,
&count))
return 0;
for(DWORD i = 0; i < count; ++i)
{
if (pSessionInfo[i].State == WTSActive)
{
ret = pSessionInfo[i].SessionId;
break;
}
}
WTSFreeMemory(pSessionInfo);
return ret;
}
void Launch()
{
if (!enablePriv(SE_DEBUG_NAME))
return;
HANDLE hToken = dupeSystemProcessToken();
if (!hToken)
{
doLog("failed to get the system process token\n");
return;
}
DWORD origSessionID, targetSessionID, returnedLen;
GetTokenInformation(hToken, TokenSessionId, &origSessionID,
sizeof(origSessionID), &returnedLen);
if (!enablePriv(SE_TCB_NAME))
goto fail_token;
targetSessionID = GetInteractiveSessionID();
if (origSessionID != targetSessionID)
{
if (!SetTokenInformation(hToken, TokenSessionId,
&targetSessionID, sizeof(targetSessionID)))
{
doLog("failed to set interactive token\n");
winerr();
goto fail_token;
}
}
LPVOID pEnvironment = NULL;
if (!CreateEnvironmentBlock(&pEnvironment, hToken, TRUE))
{
doLog("fail_tokened to create the envionment block\n");
winerr();
goto fail_token;
}
if (!enablePriv(SE_IMPERSONATE_NAME))
goto fail_token;
if (!ImpersonateLoggedOnUser(hToken))
{
doLog("fail_tokened to impersonate\n");
winerr();
goto fail_token;
}
if (!enablePriv(SE_ASSIGNPRIMARYTOKEN_NAME))
goto fail_token;
if (!enablePriv(SE_INCREASE_QUOTA_NAME))
goto fail_token;
DWORD flags = CREATE_NEW_CONSOLE | HIGH_PRIORITY_CLASS;
if (!pEnvironment)
flags |= CREATE_UNICODE_ENVIRONMENT;
PROCESS_INFORMATION pi = {0};
STARTUPINFO si =
{
.cb = sizeof(STARTUPINFO),
.dwFlags = STARTF_USESHOWWINDOW,
.wShowWindow = SW_SHOW,
.lpDesktop = "WinSta0\\Default"
};
char * exe = strdup(os_getExecutable());
if (!CreateProcessAsUserA(
hToken,
NULL,
exe,
NULL,
NULL,
TRUE,
flags,
NULL,
os_getDataPath(),
&si,
&pi
))
{
service.running = false;
doLog("failed to launch\n");
winerr();
goto fail_exe;
}
service.processId = pi.dwProcessId;
service.running = true;
fail_exe:
free(exe);
fail_token:
CloseHandle(hToken);
}
VOID SvcReportEvent(LPTSTR szFunction)
{
HANDLE hEventSource;
LPCTSTR lpszStrings[2];
TCHAR Buffer[80];
hEventSource = RegisterEventSource(NULL, SVCNAME);
if (hEventSource)
{
snprintf(Buffer, sizeof(Buffer), "%s failed with 0x%lx", szFunction, GetLastError());
lpszStrings[0] = SVCNAME;
lpszStrings[1] = Buffer;
ReportEvent(hEventSource, // event log handle
EVENTLOG_ERROR_TYPE, // event type
0, // event category
SVC_ERROR, // event identifier
NULL, // no security identifier
2, // size of lpszStrings array
0, // no binary data
lpszStrings, // array of strings
NULL); // no binary data
DeregisterEventSource(hEventSource);
}
}
void Install()
{
TCHAR szPath[MAX_PATH];
SC_HANDLE schSCManager;
SC_HANDLE schService;
if (!GetModuleFileName(NULL, szPath, MAX_PATH))
{
doLog("Cannot install service (0x%lx)\n", GetLastError());
return;
}
// Get a handle to the SCM database.
schSCManager = OpenSCManager(
NULL, // local computer
NULL, // ServicesActive database
SC_MANAGER_ALL_ACCESS); // full access rights
if (NULL == schSCManager)
{
doLog("OpenSCManager failed (0x%lx)\n", GetLastError());
return;
}
// Create the service
schService = CreateService(
schSCManager, // SCM database
SVCNAME, // name of service
SVCNAME, // service name to display
SERVICE_ALL_ACCESS, // desired access
SERVICE_WIN32_OWN_PROCESS, // service type
SERVICE_AUTO_START, // start type
SERVICE_ERROR_NORMAL, // error control type
os_getExecutable(), // path to service's binary
NULL, // no load ordering group
NULL, // no tag identifier
NULL, // no dependencies
NULL, // LocalSystem account
NULL); // no password
if (schService == NULL)
{
doLog("CreateService failed (0x%lx)\n", GetLastError());
CloseServiceHandle(schSCManager);
return;
}
else
doLog("Service installed successfully\n");
// Start the service
doLog("Starting the service\n");
StartService(schService, 0, NULL);
SERVICE_STATUS_PROCESS ssp;
DWORD dwBytesNeeded;
if (!QueryServiceStatusEx(schService, SC_STATUS_PROCESS_INFO,
(LPBYTE)&ssp, sizeof(SERVICE_STATUS_PROCESS), &dwBytesNeeded))
{
doLog("QueryServiceStatusEx failed (0x%lx)\n", GetLastError());
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
return;
}
while (ssp.dwCurrentState == SERVICE_START_PENDING)
{
DWORD dwWaitTime = ssp.dwWaitHint / 10;
if(dwWaitTime < 1000)
dwWaitTime = 1000;
else if (dwWaitTime > 10000)
dwWaitTime = 10000;
Sleep(dwWaitTime);
if (!QueryServiceStatusEx(schService, SC_STATUS_PROCESS_INFO,
(LPBYTE)&ssp, sizeof(SERVICE_STATUS_PROCESS), &dwBytesNeeded))
{
doLog("QueryServiceStatusEx failed (0x%lx)\n", GetLastError());
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
return;
}
}
if (ssp.dwCurrentState != SERVICE_RUNNING)
doLog("Failed to start the service.\n");
else
doLog("Service started.\n");
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
}
void Uninstall()
{
SC_HANDLE schSCManager;
SC_HANDLE schService;
schSCManager = OpenSCManager(
NULL, // local computer
NULL, // ServicesActive database
SC_MANAGER_ALL_ACCESS); // full access rights
if (NULL == schSCManager)
{
doLog("OpenSCManager failed (0x%lx)\n", GetLastError());
return;
}
schService = OpenService(schSCManager, SVCNAME,
SERVICE_STOP | SERVICE_QUERY_STATUS | DELETE);
if (!schService)
{
doLog("OpenService failed (0x%lx)\n", GetLastError());
CloseServiceHandle(schSCManager);
return;
}
SERVICE_STATUS_PROCESS ssp;
DWORD dwBytesNeeded;
if (!QueryServiceStatusEx(schService, SC_STATUS_PROCESS_INFO,
(LPBYTE)&ssp, sizeof(SERVICE_STATUS_PROCESS), &dwBytesNeeded))
{
doLog("QueryServiceStatusEx failed (0x%lx)\n", GetLastError());
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
return;
}
bool stop = false;
if (ssp.dwCurrentState == SERVICE_RUNNING)
{
stop = true;
doLog("Stopping the service...\n");
SERVICE_STATUS status;
if (!ControlService(schService, SERVICE_CONTROL_STOP, &status))
{
doLog("ControlService failed (%0xlx)\n", GetLastError());
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
return;
}
ssp.dwCurrentState = SERVICE_STOP_PENDING;
}
while(ssp.dwCurrentState == SERVICE_STOP_PENDING)
{
DWORD dwWaitTime = ssp.dwWaitHint / 10;
if(dwWaitTime < 1000)
dwWaitTime = 1000;
else if (dwWaitTime > 10000)
dwWaitTime = 10000;
Sleep(dwWaitTime);
if (!QueryServiceStatusEx(schService, SC_STATUS_PROCESS_INFO,
(LPBYTE)&ssp, sizeof(SERVICE_STATUS_PROCESS), &dwBytesNeeded))
{
doLog("QueryServiceStatusEx failed (0x%lx)\n", GetLastError());
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
return;
}
}
if (ssp.dwCurrentState != SERVICE_STOPPED)
{
doLog("Failed to stop the service");
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
return;
}
if (stop)
doLog("Service stopped.\n");
if (!DeleteService(schService))
{
doLog("DeleteService failed (0x%lx)\n", GetLastError());
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
return;
}
doLog("Service removed.\n");
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
}
SERVICE_STATUS gSvcStatus;
SERVICE_STATUS_HANDLE gSvcStatusHandle;
HANDLE ghSvcStopEvent = NULL;
void ReportSvcStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode,
DWORD dwWaitHint)
{
static DWORD dwCheckPoint = 1;
gSvcStatus.dwCurrentState = dwCurrentState;
gSvcStatus.dwWin32ExitCode = dwWin32ExitCode;
gSvcStatus.dwWaitHint = dwWaitHint;
if (dwCurrentState == SERVICE_START_PENDING)
gSvcStatus.dwControlsAccepted = 0;
else
gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
if ((dwCurrentState == SERVICE_RUNNING) || (dwCurrentState == SERVICE_STOPPED))
gSvcStatus.dwCheckPoint = 0;
else
gSvcStatus.dwCheckPoint = dwCheckPoint++;
SetServiceStatus(gSvcStatusHandle, &gSvcStatus);
}
VOID WINAPI SvcCtrlHandler(DWORD dwControl)
{
switch(dwControl)
{
case SERVICE_CONTROL_STOP:
ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0);
SetEvent(ghSvcStopEvent);
break;
case SERVICE_CONTROL_INTERROGATE:
break;
default:
break;
}
ReportSvcStatus(gSvcStatus.dwCurrentState, NO_ERROR, 0);
}
VOID WINAPI SvcMain(DWORD dwArgc, LPTSTR *lpszArgv)
{
gSvcStatusHandle = RegisterServiceCtrlHandler(SVCNAME, SvcCtrlHandler);
gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
gSvcStatus.dwServiceSpecificExitCode = 0;
ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 0);
ghSvcStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!ghSvcStopEvent)
{
ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
return;
}
setupLogging();
ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0);
while(1)
{
/* check if the app is running by trying to take the lock */
bool running = true;
HANDLE m = CreateMutex(NULL, FALSE, INSTANCE_MUTEX_NAME);
if (WaitForSingleObject(m, 0) == WAIT_OBJECT_0)
{
running = false;
service.running = false;
ReleaseMutex(m);
}
CloseHandle(m);
if (!running && GetInteractiveSessionID() != 0)
{
Launch();
/* avoid being overly agressive in restarting */
Sleep(1);
}
if (WaitForSingleObject(ghSvcStopEvent, 100) == WAIT_OBJECT_0)
break;
}
if (service.running)
{
doLog("Terminating the host application\n");
HANDLE proc = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, TRUE,
service.processId);
if (proc)
{
if (TerminateProcess(proc, 0))
{
while(WaitForSingleObject(proc, INFINITE) != WAIT_OBJECT_0) {}
doLog("Host application terminated\n");
}
else
doLog("Failed to terminate the host application\n");
CloseHandle(proc);
}
else
{
doLog("OpenProcess failed (%0xlx)\n", GetLastError());
}
}
ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
CloseHandle(ghSvcStopEvent);
finishLogging();
}
bool HandleService(int argc, char * argv[])
{
service.logFile = stdout;
if (argc > 1)
{
if (strcmp(argv[1], "InstallService") == 0)
{
Install();
return true;
}
if (strcmp(argv[1], "UninstallService") == 0)
{
Uninstall();
return true;
}
}
SERVICE_TABLE_ENTRY DispatchTable[] = {
{ SVCNAME, (LPSERVICE_MAIN_FUNCTION) SvcMain },
{ NULL, NULL }
};
if (StartServiceCtrlDispatcher(DispatchTable))
return true;
/* only allow one instance to run */
HANDLE m = CreateMutex(NULL, FALSE, INSTANCE_MUTEX_NAME);
if (WaitForSingleObject(m, 0) != WAIT_OBJECT_0)
{
CloseHandle(m);
return true;
}
return false;
}

View File

@@ -0,0 +1,3 @@
#include <stdbool.h>
bool HandleService(int argc, char * argv[]);

View File

@@ -62,6 +62,14 @@ static const struct LGMPQueueConfig POINTER_QUEUE_CONFIG =
#define MAX_POINTER_SIZE (sizeof(KVMFRCursor) + (128 * 128 * 4))
enum AppState
{
APP_STATE_RUNNING,
APP_STATE_IDLE,
APP_STATE_RESTART,
APP_STATE_SHUTDOWN
};
struct app
{
PLGMPHost lgmp;
@@ -81,8 +89,7 @@ struct app
CaptureInterface * iface;
bool running;
bool reinit;
enum AppState state;
LGTimer * lgmpTimer;
LGThread * frameThread;
};
@@ -95,7 +102,7 @@ static bool lgmpTimer(void * opaque)
if ((status = lgmpHostProcess(app.lgmp)) != LGMP_OK)
{
DEBUG_ERROR("lgmpHostProcess Failed: %s", lgmpStatusString(status));
app.running = false;
app.state = APP_STATE_SHUTDOWN;
return false;
}
@@ -111,7 +118,7 @@ static int frameThread(void * opaque)
CaptureFrame frame = { 0 };
const long pageSize = sysinfo_getPageSize();
while(app.running)
while(app.state == APP_STATE_RUNNING)
{
//wait until there is room in the queue
if(lgmpHostQueuePending(app.frameQueue) == LGMP_Q_FRAME_LEN)
@@ -128,7 +135,7 @@ static int frameThread(void * opaque)
case CAPTURE_RESULT_REINIT:
{
app.reinit = true;
app.state = APP_STATE_RESTART;
DEBUG_INFO("Frame thread reinit");
return 0;
}
@@ -205,7 +212,7 @@ static int frameThread(void * opaque)
bool startThreads()
{
app.running = true;
app.state = APP_STATE_RUNNING;
if (!lgCreateThread("FrameThread", frameThread, NULL, &app.frameThread))
{
DEBUG_ERROR("Failed to create the frame thread");
@@ -219,8 +226,9 @@ bool stopThreads()
{
bool ok = true;
app.running = false;
app.iface->stop();
if (app.state != APP_STATE_SHUTDOWN)
app.state = APP_STATE_IDLE;
if (app.frameThread && !lgJoinThread(app.frameThread, NULL))
{
@@ -234,7 +242,14 @@ bool stopThreads()
static bool captureStart()
{
DEBUG_INFO("Using : %s", app.iface->getName());
if (app.state == APP_STATE_IDLE)
{
if (!app.iface->init())
{
DEBUG_ERROR("Initialize the capture device");
return false;
}
}
const unsigned int maxFrameSize = app.iface->getMaxFrameSize();
if (maxFrameSize > app.maxFrameSize)
@@ -248,31 +263,33 @@ static bool captureStart()
return startThreads();
}
static bool captureRestart()
static bool captureStop()
{
DEBUG_INFO("==== [ Capture Restart ] ====");
DEBUG_INFO("==== [ Capture Stop ] ====");
if (!stopThreads())
return false;
if (!app.iface->deinit() || !app.iface->init())
if (!app.iface->deinit())
{
DEBUG_ERROR("Failed to reinitialize the capture device");
DEBUG_ERROR("Failed to deinitialize the capture device");
return false;
}
if (!captureStart())
return false;
return true;
}
static bool captureRestart()
{
return captureStop() && captureStart();
}
bool captureGetPointerBuffer(void ** data, uint32_t * size)
{
// spin until there is room
while(lgmpHostQueuePending(app.pointerQueue) == LGMP_Q_POINTER_LEN)
{
usleep(1);
if (!app.running)
if (app.state == APP_STATE_RUNNING)
return false;
}
@@ -285,10 +302,9 @@ bool captureGetPointerBuffer(void ** data, uint32_t * size)
static void sendPointer(bool newClient)
{
PLGMPMemory mem;
if (app.pointerInfo.shapeUpdate || newClient)
{
if (app.pointerInfo.shapeUpdate)
if (!newClient)
{
// swap the latest shape buffer out of rotation
PLGMPMemory tmp = app.pointerShape;
@@ -320,7 +336,8 @@ static void sendPointer(bool newClient)
if (app.pointerInfo.shapeUpdate)
{
// remember which slot has the latest shape
cursor->hx = app.pointerInfo.hx;
cursor->hy = app.pointerInfo.hy;
cursor->width = app.pointerInfo.width;
cursor->height = app.pointerInfo.height;
cursor->pitch = app.pointerInfo.pitch;
@@ -473,6 +490,7 @@ int app_main(int argc, char * argv[])
DEBUG_ERROR("lgmpHostMemAlloc Failed (Pointer): %s", lgmpStatusString(status));
goto fail;
}
memset(lgmpHostMemPtr(app.pointerMemory[i]), 0, MAX_POINTER_SIZE);
}
app.pointerShapeValid = false;
@@ -523,6 +541,9 @@ int app_main(int argc, char * argv[])
goto fail;
}
DEBUG_INFO("Using : %s", iface->getName());
app.state = APP_STATE_RUNNING;
app.iface = iface;
LG_LOCK_INIT(app.pointerLock);
@@ -533,50 +554,66 @@ int app_main(int argc, char * argv[])
goto fail;
}
if (!captureStart())
while(app.state != APP_STATE_SHUTDOWN)
{
exitcode = -1;
goto exit;
}
while(app.running)
{
if (app.reinit && !captureRestart())
if(lgmpHostQueueHasSubs(app.pointerQueue) ||
lgmpHostQueueHasSubs(app.frameQueue))
{
exitcode = -1;
goto exit;
if (!captureStart())
{
exitcode = -1;
goto exit;
}
}
app.reinit = false;
if (lgmpHostQueueNewSubs(app.pointerQueue) > 0)
else
{
LG_LOCK(app.pointerLock);
sendPointer(true);
LG_UNLOCK(app.pointerLock);
usleep(100000);
continue;
}
switch(iface->capture())
while(app.state != APP_STATE_SHUTDOWN && (
lgmpHostQueueHasSubs(app.pointerQueue) ||
lgmpHostQueueHasSubs(app.frameQueue)))
{
case CAPTURE_RESULT_OK:
break;
case CAPTURE_RESULT_TIMEOUT:
continue;
case CAPTURE_RESULT_REINIT:
if (app.state == APP_STATE_RESTART)
{
if (!captureRestart())
{
exitcode = -1;
goto exit;
}
app.reinit = false;
continue;
app.state = APP_STATE_RUNNING;
}
case CAPTURE_RESULT_ERROR:
DEBUG_ERROR("Capture interface reported a fatal error");
exitcode = -1;
goto finish;
if (lgmpHostQueueNewSubs(app.pointerQueue) > 0)
{
LG_LOCK(app.pointerLock);
sendPointer(true);
LG_UNLOCK(app.pointerLock);
}
switch(iface->capture())
{
case CAPTURE_RESULT_OK:
break;
case CAPTURE_RESULT_TIMEOUT:
continue;
case CAPTURE_RESULT_REINIT:
app.state = APP_STATE_RESTART;
continue;
case CAPTURE_RESULT_ERROR:
DEBUG_ERROR("Capture interface reported a fatal error");
exitcode = -1;
goto finish;
}
}
if (app.state != APP_STATE_SHUTDOWN)
DEBUG_INFO("No subscribers, going to sleep...");
captureStop();
}
finish:
@@ -603,5 +640,5 @@ fail:
void app_quit()
{
app.running = false;
app.state = APP_STATE_SHUTDOWN;
}