Compare commits

..

82 Commits
B2-rc2 ... B2

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
Geoffrey McRae
82e0b7b6ab [doc] readme updated with PsExec information 2020-08-09 20:11:19 +10:00
Geoffrey McRae
2e1b0f2550 [all] update the LGMP submodule 2020-08-09 18:13:43 +10:00
Geoffrey McRae
3302d353cf [client] always use spice mouse host mode
Since we only ever use offset movements as SPICE doesn't properly
support absolute x/y positional information without a virtual tablet
device (which breaks relative mode needed for capture), just always run
in this mode. This fixes an issue when the spice guest tools are
installed and the mouse fails to work when not captured.
2020-08-09 16:17:08 +10:00
Geoffrey McRae
1899d9f1da [client] reset the frame time when we get a frame signal
This stops a duplicate frame rendering bug due to failure to discipline
based on the signal timing.
2020-08-09 15:55:12 +10:00
Geoffrey McRae
fb9b772db0 [client] we are getting the clock anyway, just reset the time 2020-08-09 15:54:45 +10:00
Geoffrey McRae
302b988524 [client] use atomics to track frame counts and avoid extra signals 2020-08-09 15:14:17 +10:00
Geoffrey McRae
19c2fe9b5e Revert "[common] linux: improve event mechanics"
The logic here is wrong, this should be done externally as multiple
waiters will cause issues
2020-08-09 14:44:00 +10:00
Geoffrey McRae
88d25ee98c [common] linux: improve event mechanics 2020-08-09 13:26:55 +10:00
Geoffrey McRae
0f2ecdf5f1 [obs] cosmetic 2020-08-09 12:31:56 +10:00
Geoffrey McRae
3511fb8d59 [obs] microsttuer fix, be sure to always grab the latest frame 2020-08-09 12:29:52 +10:00
Geoffrey McRae
1d6d640b6e [host] dxgi: default to using the acquire lock 2020-08-07 20:31:46 +10:00
Geoffrey McRae
977d7b277d [host] dxgi: boost GPU thread priority if possible 2020-08-07 19:44:00 +10:00
Geoffrey McRae
be7820303f [common] fixed debug formatting across platforms 2020-08-03 15:05:35 +10:00
Geoffrey McRae
43503222c7 [common] framebuffer: fixed incorrect streaming usage 2020-08-03 14:41:57 +10:00
Geoffrey McRae
85b8c12abf [common] adjust framebuffer read/write strategy for better cache usage 2020-08-03 12:33:08 +10:00
Geoffrey McRae
7af053497e [common] unroll the framebuffer write loop and increase the chunk size 2020-08-03 12:24:17 +10:00
Geoffrey McRae
9e3a42cb62 [host] don't stop the timer when restarting capture 2020-08-03 12:04:50 +10:00
Geoffrey McRae
aa32c5ffad [common] framebuffer: added missing header include 2020-08-03 11:58:38 +10:00
Geoffrey McRae
62d1bd1ea2 [common] framebuffer: use stream load instead of plain load 2020-08-03 11:55:38 +10:00
Geoffrey McRae
2329e993ee [common] fixed framebuffer write SIMD code performance 2020-08-03 11:44:24 +10:00
Geoffrey McRae
da655b86c3 [common] improve frambuffer copy to avoid cache pollution (SIMD) 2020-08-03 11:16:30 +10:00
Max Sistemich
c5ff8bd4ce [common] linux: implement timers 2020-07-25 00:38:15 +10:00
Geoffrey McRae
06aee158de [client] egl: make better use of atomics and fix modulus bug 2020-07-24 17:39:16 +10:00
Samuel Bowman
bd42445ea7 [client] add option to capture input on start 2020-07-17 08:39:32 +10:00
Geoffrey McRae
ede96fa486 [client] egl: don't map the texture until it's needed
The texture buffer may still be in use if we try to re-map it
immediately, instead only map when we need it mapped, and unmap
immediately after advancing the offset allowing the render thread to
continue while the unmap operation occurs
2020-05-30 16:50:27 +10:00
Geoffrey McRae
67dec216d2 [host] search the applications local directory for the config 2020-05-30 12:31:26 +10:00
Geoffrey McRae
fcbdf7ba4f [client] egl: fix non-streaming texture updates 2020-05-29 16:54:25 +10:00
Geoffrey McRae
e8c949c1e7 [client] egl: dont re-setup the fps texture on each update 2020-05-29 16:47:21 +10:00
Geoffrey McRae
28c93ef5ac [client] egl: don't unmap/map all buffers for each frame 2020-05-29 15:48:59 +10:00
Geoffrey McRae
d7921c5d5f [client] report the host version on mismatch if possible 2020-05-29 14:24:06 +10:00
Geoffrey McRae
6d296f2b44 [client] stop people running the client as root 2020-05-29 14:18:02 +10:00
Geoffrey McRae
553e2830bb [client/host] share the host version with the client for diagnostics 2020-05-29 14:14:31 +10:00
Geoffrey McRae
667ab981ba [host] send the latest cusror information when a new client connects 2020-05-25 14:37:02 +10:00
Geoffrey McRae
bc7871f630 [c-host] renamed finall to just plain host 2020-05-25 13:42:43 +10:00
59 changed files with 2092 additions and 473 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)
* [c-host/README.md](c-host/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-rc1-10-g94d383a8c1+1
B2-rc4-11-g8692e9af80+1

View File

@@ -142,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

@@ -44,6 +44,7 @@ struct EGL_FPS
EGL_Model * model;
bool ready;
int iwidth, iheight;
float width, height;
// uniforms
@@ -144,14 +145,22 @@ void egl_fps_update(EGL_FPS * fps, const float avgFPS, const float renderFPS)
return;
}
egl_texture_setup(
fps->texture,
EGL_PF_BGRA,
bmp->width ,
bmp->height,
bmp->width * bmp->bpp,
false
);
if (fps->iwidth != bmp->width || fps->iheight != bmp->height)
{
fps->iwidth = bmp->width;
fps->iheight = bmp->height;
fps->width = (float)bmp->width;
fps->height = (float)bmp->height;
egl_texture_setup(
fps->texture,
EGL_PF_BGRA,
bmp->width ,
bmp->height,
bmp->width * bmp->bpp,
false
);
}
egl_texture_update
(
@@ -159,10 +168,7 @@ void egl_fps_update(EGL_FPS * fps, const float avgFPS, const float renderFPS)
bmp->pixels
);
fps->width = bmp->width;
fps->height = bmp->height;
fps->ready = true;
fps->font->release(fps->fontObj, bmp);
}
@@ -187,4 +193,4 @@ void egl_fps_render(EGL_FPS * fps, const float scaleX, const float scaleY)
egl_model_render(fps->model);
glDisable(GL_BLEND);
}
}

View File

@@ -30,7 +30,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <SDL2/SDL_egl.h>
#define TEXTURE_COUNT 3
/* this must be a multiple of 2 */
#define TEXTURE_COUNT 2
struct Tex
{
@@ -41,19 +42,9 @@ struct Tex
GLsync sync;
};
union TexState
struct TexState
{
_Atomic(uint32_t) v;
struct
{
/*
* w = write
* u = upload
* s = schedule
* d = display
*/
_Atomic(int8_t) w, u, s, d;
};
_Atomic(uint8_t) w, u, s, d;
};
struct EGL_Texture
@@ -73,8 +64,9 @@ struct EGL_Texture
GLenum dataType;
size_t pboBufferSize;
union TexState state;
struct Tex tex[TEXTURE_COUNT];
struct TexState state;
int textureCount;
struct Tex tex[TEXTURE_COUNT];
};
bool egl_texture_init(EGL_Texture ** texture)
@@ -98,7 +90,7 @@ void egl_texture_free(EGL_Texture ** texture)
if ((*texture)->planeCount > 0)
glDeleteSamplers((*texture)->planeCount, (*texture)->samplers);
for(int i = 0; i < ((*texture)->streaming ? TEXTURE_COUNT : 1); ++i)
for(int i = 0; i < (*texture)->textureCount; ++i)
{
struct Tex * t = &(*texture)->tex[i];
if (t->hasPBO)
@@ -123,56 +115,68 @@ void egl_texture_free(EGL_Texture ** texture)
*texture = NULL;
}
static bool egl_texture_map(EGL_Texture * texture)
static bool egl_texture_map(EGL_Texture * texture, uint8_t i)
{
// release old PBOs and delete and re-create the buffers
for(int i = 0; i < TEXTURE_COUNT; ++i)
{
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[i].pbo);
texture->tex[i].map = glMapBufferRange(
GL_PIXEL_UNPACK_BUFFER,
0,
texture->pboBufferSize,
GL_MAP_WRITE_BIT |
GL_MAP_UNSYNCHRONIZED_BIT |
GL_MAP_INVALIDATE_BUFFER_BIT
);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[i].pbo);
texture->tex[i].map = glMapBufferRange(
GL_PIXEL_UNPACK_BUFFER,
0,
texture->pboBufferSize,
GL_MAP_WRITE_BIT |
GL_MAP_UNSYNCHRONIZED_BIT |
GL_MAP_INVALIDATE_BUFFER_BIT
);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
if (!texture->tex[i].map)
{
EGL_ERROR("glMapBufferRange failed for %d of %lu bytes", i, texture->pboBufferSize);
return false;
}
if (!texture->tex[i].map)
{
EGL_ERROR("glMapBufferRange failed for %d of %lu bytes", i, texture->pboBufferSize);
return false;
}
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
return true;
}
static void egl_texture_unmap(EGL_Texture * texture)
static void egl_texture_unmap(EGL_Texture * texture, uint8_t i)
{
for(int i = 0; i < TEXTURE_COUNT; ++i)
{
if (!texture->tex[i].map)
continue;
if (!texture->tex[i].map)
return;
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[i].pbo);
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
texture->tex[i].map = NULL;
}
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[i].pbo);
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
texture->tex[i].map = NULL;
}
bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_t width, size_t height, size_t stride, bool streaming)
{
int planeCount;
texture->pixFmt = pixFmt;
texture->width = width;
texture->height = height;
texture->stride = stride;
texture->streaming = streaming;
texture->ready = false;
atomic_store_explicit(&texture->state.v, 0, memory_order_relaxed);
if (texture->streaming)
{
for(int i = 0; i < texture->textureCount; ++i)
{
egl_texture_unmap(texture, i);
if (texture->tex[i].hasPBO)
{
glDeleteBuffers(1, &texture->tex[i].pbo);
texture->tex[i].hasPBO = false;
}
}
}
texture->pixFmt = pixFmt;
texture->width = width;
texture->height = height;
texture->stride = stride;
texture->streaming = streaming;
texture->textureCount = streaming ? TEXTURE_COUNT : 1;
texture->ready = false;
atomic_store_explicit(&texture->state.w, 0, memory_order_relaxed);
atomic_store_explicit(&texture->state.u, 0, memory_order_relaxed);
atomic_store_explicit(&texture->state.s, 0, memory_order_relaxed);
atomic_store_explicit(&texture->state.d, 0, memory_order_relaxed);
switch(pixFmt)
{
@@ -245,7 +249,7 @@ bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_
if (texture->planeCount > 0)
glDeleteSamplers(texture->planeCount, texture->samplers);
for(int i = 0; i < TEXTURE_COUNT; ++i)
for(int i = 0; i < texture->textureCount; ++i)
{
if (texture->planeCount > 0)
glDeleteTextures(texture->planeCount, texture->tex[i].t);
@@ -264,7 +268,7 @@ bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_
texture->planeCount = planeCount;
}
for(int i = 0; i < (streaming ? TEXTURE_COUNT : 1); ++i)
for(int i = 0; i < texture->textureCount; ++i)
{
for(int p = 0; p < planeCount; ++p)
{
@@ -275,14 +279,11 @@ bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_
}
glBindTexture(GL_TEXTURE_2D, 0);
egl_texture_unmap(texture);
if (!streaming)
return true;
// release old PBOs and delete and re-create the buffers
for(int i = 0; i < TEXTURE_COUNT; ++i)
for(int i = 0; i < texture->textureCount; ++i)
{
if (texture->tex[i].hasPBO)
glDeleteBuffers(1, &texture->tex[i].pbo);
glGenBuffers(1, &texture->tex[i].pbo);
texture->tex[i].hasPBO = true;
@@ -295,9 +296,6 @@ bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_
);
}
if (!egl_texture_map(texture))
return false;
return true;
}
@@ -318,23 +316,25 @@ bool egl_texture_update(EGL_Texture * texture, const uint8_t * buffer)
{
if (texture->streaming)
{
union TexState s;
s.v = atomic_load_explicit(&texture->state.v, memory_order_acquire);
const uint8_t sw =
atomic_load_explicit(&texture->state.w, memory_order_acquire);
const uint8_t next = (s.w + 1) % TEXTURE_COUNT;
if (next == s.u)
if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == (uint8_t)(sw + 1))
{
egl_warn_slow();
return true;
}
memcpy(texture->tex[s.w].map, buffer, texture->pboBufferSize);
atomic_store_explicit(&texture->state.w, next, memory_order_release);
const uint8_t t = sw % TEXTURE_COUNT;
if (!egl_texture_map(texture, t))
return EGL_TEX_STATUS_ERROR;
memcpy(texture->tex[t].map, buffer, texture->pboBufferSize);
atomic_fetch_add_explicit(&texture->state.w, 1, memory_order_release);
egl_texture_unmap(texture, t);
}
else
{
/* Non streaming, this is NOT thread safe */
for(int p = 0; p < texture->planeCount; ++p)
{
glBindTexture(GL_TEXTURE_2D, texture->tex[0].t[p]);
@@ -352,19 +352,22 @@ bool egl_texture_update_from_frame(EGL_Texture * texture, const FrameBuffer * fr
if (!texture->streaming)
return false;
union TexState s;
s.v = atomic_load_explicit(&texture->state.v, memory_order_acquire);
const uint8_t sw =
atomic_load_explicit(&texture->state.w, memory_order_acquire);
const uint8_t next = (s.w + 1) % TEXTURE_COUNT;
if (next == s.u)
if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == (uint8_t)(sw + 1))
{
egl_warn_slow();
return true;
}
const uint8_t t = sw % TEXTURE_COUNT;
if (!egl_texture_map(texture, t))
return EGL_TEX_STATUS_ERROR;
framebuffer_read(
frame,
texture->tex[s.w].map,
texture->tex[t].map,
texture->stride,
texture->height,
texture->width,
@@ -372,7 +375,9 @@ bool egl_texture_update_from_frame(EGL_Texture * texture, const FrameBuffer * fr
texture->stride
);
atomic_store_explicit(&texture->state.w, next, memory_order_release);
atomic_fetch_add_explicit(&texture->state.w, 1, memory_order_release);
egl_texture_unmap(texture, t);
return true;
}
@@ -381,62 +386,63 @@ enum EGL_TexStatus egl_texture_process(EGL_Texture * texture)
if (!texture->streaming)
return EGL_TEX_STATUS_OK;
union TexState s;
s.v = atomic_load_explicit(&texture->state.v, memory_order_acquire);
const uint8_t su =
atomic_load_explicit(&texture->state.u, memory_order_acquire);
const uint8_t nextu = (s.u + 1) % TEXTURE_COUNT;
if (s.u == s.w || nextu == s.s || nextu == s.d)
const uint8_t nextu = su + 1;
if (
su == atomic_load_explicit(&texture->state.w, memory_order_acquire) ||
nextu == atomic_load_explicit(&texture->state.s, memory_order_acquire) ||
nextu == atomic_load_explicit(&texture->state.d, memory_order_acquire))
return texture->ready ? EGL_TEX_STATUS_OK : EGL_TEX_STATUS_NOTREADY;
/* update the texture */
egl_texture_unmap(texture);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[s.u].pbo);
const uint8_t t = su % TEXTURE_COUNT;
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[t].pbo);
for(int p = 0; p < texture->planeCount; ++p)
{
glBindTexture(GL_TEXTURE_2D, texture->tex[s.u].t[p]);
glBindTexture(GL_TEXTURE_2D, texture->tex[t].t[p]);
glPixelStorei(GL_UNPACK_ROW_LENGTH, texture->planes[p][2]);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture->planes[p][0], texture->planes[p][1],
texture->format, texture->dataType, (const void *)texture->offsets[p]);
}
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
/* create a fence to prevent usage before the update is complete */
texture->tex[s.u].sync =
glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
texture->tex[t].sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
/* we must flush to ensure the sync is in the command buffer */
glFlush();
atomic_store_explicit(&texture->state.u, nextu, memory_order_release);
/* remap the for the next update */
egl_texture_map(texture);
texture->ready = true;
atomic_fetch_add_explicit(&texture->state.u, 1, memory_order_release);
return EGL_TEX_STATUS_OK;
}
enum EGL_TexStatus egl_texture_bind(EGL_Texture * texture)
{
union TexState s;
s.v = atomic_load_explicit(&texture->state.v, memory_order_acquire);
uint8_t ss = atomic_load_explicit(&texture->state.s, memory_order_acquire);
uint8_t sd = atomic_load_explicit(&texture->state.d, memory_order_acquire);
if (texture->streaming)
{
if (!texture->ready)
return EGL_TEX_STATUS_NOTREADY;
if (texture->tex[s.s].sync != 0)
const uint8_t t = ss % TEXTURE_COUNT;
if (texture->tex[t].sync != 0)
{
switch(glClientWaitSync(texture->tex[s.s].sync, 0, 20000000)) // 20ms
switch(glClientWaitSync(texture->tex[t].sync, 0, 20000000)) // 20ms
{
case GL_ALREADY_SIGNALED:
case GL_CONDITION_SATISFIED:
glDeleteSync(texture->tex[s.s].sync);
texture->tex[s.s].sync = 0;
glDeleteSync(texture->tex[t].sync);
texture->tex[t].sync = 0;
s.s = (s.s + 1) % TEXTURE_COUNT;
atomic_store_explicit(&texture->state.s, s.s, memory_order_release);
ss = atomic_fetch_add_explicit(&texture->state.s, 1,
memory_order_release) + 1;
break;
case GL_TIMEOUT_EXPIRED:
@@ -444,25 +450,23 @@ enum EGL_TexStatus egl_texture_bind(EGL_Texture * texture)
case GL_WAIT_FAILED:
case GL_INVALID_VALUE:
glDeleteSync(texture->tex[s.s].sync);
texture->tex[s.s].sync = 0;
glDeleteSync(texture->tex[t].sync);
texture->tex[t].sync = 0;
EGL_ERROR("glClientWaitSync failed");
return EGL_TEX_STATUS_ERROR;
}
}
const int8_t nextd = (s.d + 1) % TEXTURE_COUNT;
if (s.d != s.s && nextd != s.s)
{
s.d = nextd;
atomic_store_explicit(&texture->state.d, nextd, memory_order_release);
}
if (ss != sd && ss != (uint8_t)(sd + 1))
sd = atomic_fetch_add_explicit(&texture->state.d, 1,
memory_order_release) + 1;
}
const uint8_t t = sd % TEXTURE_COUNT;
for(int i = 0; i < texture->planeCount; ++i)
{
glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(GL_TEXTURE_2D, texture->tex[s.d].t[i]);
glBindTexture(GL_TEXTURE_2D, texture->tex[t].t[i]);
glBindSampler(i, texture->samplers[i]);
}

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
{
@@ -309,6 +316,13 @@ static struct Option options[] =
.type = OPTION_TYPE_BOOL,
.value.x_bool = true
},
{
.module = "spice",
.name = "captureOnStart",
.description = "Capture mouse and keyboard on start",
.type = OPTION_TYPE_BOOL,
.value.x_bool = false
},
{0}
};
@@ -392,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");
@@ -413,6 +428,7 @@ bool config_load(int argc, char * argv[])
}
params.scaleMouseInput = option_get_bool("spice", "scaleCursor");
params.captureOnStart = option_get_bool("spice", "captureOnStart");
}
return true;

View File

@@ -34,6 +34,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
#include <stdatomic.h>
#if SDL_VIDEO_DRIVER_X11_XINPUT2
// because SDL2 sucks and we need to turn it off
@@ -69,6 +70,8 @@ static LGThread *t_cursor = NULL;
static LGThread *t_frame = NULL;
static SDL_Cursor *cursor = NULL;
static atomic_uint a_framesPending = 0;
struct AppState state;
// this structure is initialized in config.c
@@ -138,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);
@@ -152,26 +155,16 @@ 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)
{
tsAdd(&time, state.frameTime);
// if our clock is too far out of sync, resync it
// this can happen when switching to/from a TTY, or due to clock drift
// we only check this once every 100 frames
if (++resyncCheck == 100)
{
resyncCheck = 0;
struct timespec end, diff;
clock_gettime(CLOCK_REALTIME, &end);
tsDiff(&diff, &time, &end);
if (diff.tv_sec > 0 || diff.tv_nsec > 1000000000 || // 100ms
diff.tv_sec < 0 || diff.tv_nsec < 0) // underflow
clock_gettime(CLOCK_REALTIME, &time);
clock_gettime(CLOCK_REALTIME, &time);
}
tsAdd(&time, state.frameTime);
}
if (state.lgrResize)
@@ -193,12 +186,12 @@ static int renderThread(void * unused)
if (state.renderTime > 1e9)
{
const float avgUPS = 1000.0f / (((float)state.renderTime / state.frameCount ) / 1e6f);
const float avgUPS = 1000.0f / (((float)state.renderTime / atomic_load_explicit(&state.frameCount, memory_order_acquire)) / 1e6f);
const float avgFPS = 1000.0f / (((float)state.renderTime / state.renderCount) / 1e6f);
state.lgr->update_fps(state.lgrData, avgUPS, avgFPS);
atomic_store_explicit(&state.frameCount, 0, memory_order_release);
state.renderTime = 0;
state.frameCount = 0;
state.renderCount = 0;
}
}
@@ -214,10 +207,29 @@ static int renderThread(void * unused)
}
if (state.frameTime > 0)
lgWaitEventAbs(e_frame, &time);
{
/* if there are frames pending already, don't wait on the event */
if (atomic_load_explicit(&a_framesPending, memory_order_acquire) > 0)
if (atomic_fetch_sub_explicit(&a_framesPending, 1, memory_order_release) > 1)
continue;
if (lgWaitEventAbs(e_frame, &time) && 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;
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);
@@ -239,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)
@@ -252,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)
@@ -269,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;
}
@@ -289,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)
@@ -315,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,
@@ -331,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;
@@ -341,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;
}
@@ -355,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)
@@ -372,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)
@@ -387,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;
}
@@ -427,6 +454,7 @@ static int frameThread(void * unused)
if (error)
{
lgmpClientMessageDone(queue);
state.state = APP_STATE_SHUTDOWN;
break;
}
@@ -445,33 +473,35 @@ 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;
}
lgmpClientMessageDone(queue);
++state.frameCount;
lgSignalEvent(e_frame);
atomic_fetch_add_explicit(&state.frameCount, 1, memory_order_relaxed);
if (atomic_fetch_add_explicit(&a_framesPending, 1, memory_order_relaxed) == 0)
lgSignalEvent(e_frame);
lgmpClientMessageDone(queue);
}
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;
}
@@ -647,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;
@@ -659,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
@@ -692,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;
}
}
@@ -700,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;
@@ -739,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()
@@ -752,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);
}
@@ -775,6 +819,7 @@ static void handleWindowLeave()
state.drawCursor = false;
state.cursorInView = false;
state.updateCursor = true;
state.warpState = WARP_STATE_OFF;
}
static void handleWindowEnter()
@@ -785,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)
@@ -796,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;
}
@@ -823,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;
@@ -919,7 +965,6 @@ int eventFilter(void * userdata, SDL_Event * event)
if (params.useSpiceInput)
{
state.serverMode = !state.serverMode;
spice_mouse_mode(state.serverMode);
SDL_SetWindowGrab(state.window, state.serverMode);
DEBUG_INFO("Server Mode: %s", state.serverMode ? "on" : "off");
@@ -928,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();
}
}
@@ -1024,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;
}
}
@@ -1072,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)
@@ -1155,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;
@@ -1218,14 +1265,15 @@ 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;
}
spice_mouse_mode(true);
if (!lgCreateThread("spiceThread", spiceThread, NULL, &t_spice))
{
DEBUG_ERROR("spice create thread failed");
@@ -1310,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();
@@ -1394,6 +1444,13 @@ static int lg_run()
SDL_ShowCursor(SDL_DISABLE);
}
if (params.captureOnStart)
{
state.serverMode = true;
SDL_SetWindowGrab(state.window, state.serverMode);
DEBUG_INFO("Server Mode: %s", state.serverMode ? "on" : "off");
}
// setup the startup condition
if (!(e_startup = lgCreateEvent(false, 0)))
{
@@ -1425,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;
@@ -1440,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;
@@ -1471,22 +1529,35 @@ static int lg_run()
SDL_WaitEventTimeout(NULL, 1000);
}
if (!state.running)
if (state.state != APP_STATE_RUNNING)
return -1;
if (udataSize != sizeof(KVMFR) ||
memcmp(udata->magic, KVMFR_MAGIC, sizeof(udata->magic)) != 0 ||
udata->version != KVMFR_VERSION)
// 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)
{
DEBUG_BREAK();
DEBUG_ERROR("The host application is not compatible with this client");
DEBUG_ERROR("Expected KVMFR version %d", KVMFR_VERSION);
DEBUG_ERROR("This is not a Looking Glass error, do not report this");
DEBUG_ERROR("Please install the matching host application for this client");
if (magicMatches)
{
DEBUG_ERROR("Expected KVMFR version %d, got %d", KVMFR_VERSION, udata->version);
if (udata->version >= 2)
DEBUG_ERROR("Host version: %s", udata->hostver);
}
else
DEBUG_ERROR("Invalid KVMFR magic");
DEBUG_BREAK();
return -1;
}
DEBUG_INFO("Host ready, starting session");
DEBUG_INFO("Host ready, reported version: %s", udata->hostver);
DEBUG_INFO("Starting session");
if (!lgCreateThread("cursorThread", cursorThread, NULL, &t_cursor))
{
@@ -1500,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;
@@ -1515,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);
@@ -1580,6 +1666,12 @@ static void lg_shutdown()
int main(int argc, char * argv[])
{
if (getuid() == 0)
{
DEBUG_ERROR("Do not run looking glass as root!");
return -1;
}
DEBUG_INFO("Looking Glass (" BUILD_VERSION ")");
DEBUG_INFO("Locking Method: " LG_LOCK_MODE);

View File

@@ -18,6 +18,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdbool.h>
#include <stdatomic.h>
#include <SDL2/SDL.h>
#include "interface/app.h"
@@ -28,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;
@@ -41,7 +63,7 @@ struct AppState
int windowW, windowH;
SDL_Point srcSize;
LG_RendererRect dstRect;
SDL_Point cursor;
struct CursorInfo cursor;
bool cursorVisible;
bool serverMode;
@@ -59,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;
@@ -75,11 +101,11 @@ struct AppState
PLGMPClientQueue frameQueue;
PLGMPClientQueue pointerQueue;
uint64_t frameTime;
uint64_t lastFrameTime;
uint64_t renderTime;
uint64_t frameCount;
uint64_t renderCount;
atomic_uint_least64_t frameTime;
uint64_t lastFrameTime;
uint64_t renderTime;
uint64_t frameCount;
uint64_t renderCount;
uint64_t resizeTimeout;
@@ -124,6 +150,7 @@ struct AppParams
bool grabKeyboard;
SDL_Scancode escapeKey;
bool showAlerts;
bool captureOnStart;
unsigned int cursorPollInterval;
unsigned int framePollInterval;
@@ -133,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,12 +51,13 @@ typedef enum CursorType
CursorType;
#define KVMFR_MAGIC "KVMFR---"
#define KVMFR_VERSION 1
#define KVMFR_VERSION 3
typedef struct KVMFR
{
char magic[8];
uint32_t version;
char hostver[32];
}
KVMFR;
@@ -64,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

@@ -53,7 +53,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
sizeof(s) > 20 && (s)[sizeof(s)-21] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 20 : \
sizeof(s) > 21 && (s)[sizeof(s)-22] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 21 : (s))
#define DEBUG_PRINT(type, fmt, ...) do {fprintf(stderr, "%" PRId64 " " type " %20s:%-4u | %-30s | " fmt "\n", microtime(), STRIPPATH(__FILE__), __LINE__, __FUNCTION__, ##__VA_ARGS__);} while (0)
#define DEBUG_PRINT(type, fmt, ...) do {fprintf(stderr, "%12" PRId64 " " type " %20s:%-4u | %-30s | " fmt "\n", microtime(), STRIPPATH(__FILE__), __LINE__, __FUNCTION__, ##__VA_ARGS__);} while (0)
#define DEBUG_BREAK() DEBUG_PRINT("[ ]", "%s", "================================================================================")
#define DEBUG_INFO(fmt, ...) DEBUG_PRINT("[I]", fmt, ##__VA_ARGS__)

View File

@@ -22,8 +22,12 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <string.h>
#include <stdatomic.h>
#include <emmintrin.h>
#include <smmintrin.h>
#include <unistd.h>
#define FB_CHUNK_SIZE 1024
#define FB_CHUNK_SIZE 1048576 // 1MB
#define FB_SPIN_LIMIT 10000 // 10ms
struct stFrameBuffer
{
@@ -35,31 +39,63 @@ const size_t FrameBufferStructSize = sizeof(FrameBuffer);
void framebuffer_wait(const FrameBuffer * frame, size_t size)
{
while(atomic_load_explicit(&frame->wp, memory_order_relaxed) != size) {}
while(atomic_load_explicit(&frame->wp, memory_order_acquire) != size) {}
}
bool framebuffer_read(const FrameBuffer * frame, void * dst, size_t dstpitch,
size_t height, size_t width, size_t bpp, size_t pitch)
bool framebuffer_read(const FrameBuffer * frame, void * restrict dst,
size_t dstpitch, size_t height, size_t width, size_t bpp, size_t pitch)
{
uint8_t *d = (uint8_t*)dst;
uint8_t * restrict d = (uint8_t*)dst;
uint_least32_t rp = 0;
size_t y = 0;
const size_t linewidth = width * bpp;
const size_t blocks = linewidth / 64;
const size_t left = linewidth % 64;
while(y < height)
{
uint_least32_t wp;
int spinCount = 0;
/* spinlock */
do
wp = atomic_load_explicit(&frame->wp, memory_order_relaxed);
while(wp - rp < pitch);
wp = atomic_load_explicit(&frame->wp, memory_order_acquire);
while(wp - rp < linewidth)
{
if (++spinCount == FB_SPIN_LIMIT)
return false;
memcpy(d, frame->data + rp, linewidth);
usleep(1);
wp = atomic_load_explicit(&frame->wp, memory_order_acquire);
}
_mm_mfence();
__m128i * restrict s = (__m128i *)(frame->data + rp);
for(int i = 0; i < blocks; ++i)
{
__m128i *_d = (__m128i *)d;
__m128i *_s = (__m128i *)s;
__m128i v1 = _mm_stream_load_si128(_s + 0);
__m128i v2 = _mm_stream_load_si128(_s + 1);
__m128i v3 = _mm_stream_load_si128(_s + 2);
__m128i v4 = _mm_stream_load_si128(_s + 3);
_mm_storeu_si128(_d + 0, v1);
_mm_storeu_si128(_d + 1, v2);
_mm_storeu_si128(_d + 2, v3);
_mm_storeu_si128(_d + 3, v4);
d += 64;
s += 4;
}
if (left)
{
memcpy(d, s, left);
d += left;
}
rp += pitch;
d += dstpitch;
d += dstpitch - linewidth;
++y;
}
@@ -76,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_relaxed);
while(wp - rp < pitch);
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);
}
if (!fn(opaque, frame->data + rp, linewidth))
return false;
@@ -97,18 +140,47 @@ bool framebuffer_read_fn(const FrameBuffer * frame, size_t height, size_t width,
*/
void framebuffer_prepare(FrameBuffer * frame)
{
atomic_store(&frame->wp, 0);
atomic_store_explicit(&frame->wp, 0, memory_order_release);
}
bool framebuffer_write(FrameBuffer * frame, const void * src, size_t size)
bool framebuffer_write(FrameBuffer * frame, const void * restrict src, size_t size)
{
__m128i * restrict s = (__m128i *)src;
__m128i * restrict d = (__m128i *)frame->data;
size_t wp = 0;
_mm_mfence();
/* copy in chunks */
while(size)
while(size > 63)
{
size_t copy = size < FB_CHUNK_SIZE ? FB_CHUNK_SIZE : size;
memcpy(frame->data + frame->wp, src, copy);
atomic_fetch_add(&frame->wp, copy);
size -= copy;
__m128i *_d = (__m128i *)d;
__m128i *_s = (__m128i *)s;
__m128i v1 = _mm_stream_load_si128(_s + 0);
__m128i v2 = _mm_stream_load_si128(_s + 1);
__m128i v3 = _mm_stream_load_si128(_s + 2);
__m128i v4 = _mm_stream_load_si128(_s + 3);
_mm_store_si128(_d + 0, v1);
_mm_store_si128(_d + 1, v2);
_mm_store_si128(_d + 2, v3);
_mm_store_si128(_d + 3, v4);
s += 4;
d += 4;
size -= 64;
wp += 64;
if (wp % FB_CHUNK_SIZE == 0)
atomic_store_explicit(&frame->wp, wp, memory_order_release);
}
if(size)
{
memcpy(frame->data + wp, s, size);
wp += size;
}
atomic_store_explicit(&frame->wp, wp, memory_order_release);
return true;
}

View File

@@ -11,6 +11,7 @@ add_library(lg_common_platform_code STATIC
thread.c
event.c
ivshmem.c
time.c
)
if(ENABLE_BACKTRACE)
@@ -20,4 +21,5 @@ endif()
target_link_libraries(lg_common_platform_code
lg_common
pthread
rt
)

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

@@ -0,0 +1,109 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2020-2020 Max Sistemich <maximilian.sistemich@rwth-aachen.de>
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 "common/time.h"
#include "common/debug.h"
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
struct LGTimer
{
LGTimerFn fn;
void * udata;
timer_t id;
bool running;
};
static void TimerProc(union sigval arg)
{
LGTimer * timer = (LGTimer *)arg.sival_ptr;
if (!timer->fn(timer->udata))
{
if (timer_delete(timer->id))
DEBUG_ERROR("failed to destroy the timer: %s", strerror(errno));
timer->running = false;
}
}
bool lgCreateTimer(const unsigned int intervalMS, LGTimerFn fn,
void * udata, LGTimer ** result)
{
LGTimer * ret = malloc(sizeof(LGTimer));
if (!ret)
{
DEBUG_ERROR("failed to malloc LGTimer struct");
return false;
}
ret->fn = fn;
ret->udata = udata;
ret->running = true;
struct sigevent sev =
{
.sigev_notify = SIGEV_THREAD,
.sigev_notify_function = &TimerProc,
.sigev_value.sival_ptr = ret,
};
if (timer_create(CLOCK_MONOTONIC, &sev, &ret->id))
{
DEBUG_ERROR("failed to create timer: %s", strerror(errno));
free(ret);
return false;
}
struct timespec interval =
{
.tv_sec = 0,
.tv_nsec = intervalMS * 1000 * 1000,
};
struct itimerspec spec =
{
.it_interval = interval,
.it_value = interval,
};
if (timer_settime(ret->id, 0, &spec, NULL))
{
DEBUG_ERROR("failed to set timer: %s", strerror(errno));
timer_delete(ret->id);
free(ret);
return false;
}
*result = ret;
return true;
}
void lgTimerDestroy(LGTimer * timer)
{
if (timer->running)
{
if (timer_delete(timer->id))
DEBUG_ERROR("failed to destroy the timer: %s", strerror(errno));
}
free(timer);
}

View File

@@ -37,7 +37,7 @@ void DebugWinError(const char * file, const unsigned int line, const char * func
if (buffer[i] == '\n' || buffer[i] == '\r')
buffer[i] = 0;
fprintf(stderr, "[E] %20s:%-4u | %-30s | %s: 0x%08x (%s)\n", file, line, function, desc, (int)status, buffer);
fprintf(stderr, "%12" PRId64 " [E] %20s:%-4u | %-30s | %s: 0x%08x (%s)\n", microtime(), file, line, function, desc, (int)status, buffer);
LocalFree(buffer);
}
@@ -63,4 +63,4 @@ bool IsWindows8()
return
(CompareWindowsVersion(6, 3) == TRUE) ||
(CompareWindowsVersion(6, 2) == 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

@@ -27,3 +27,4 @@ void app_quit();
// these must be implemented for each OS
const char * os_getExecutable();
const char * os_getDataPath();

View File

@@ -30,6 +30,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
struct app
{
const char * executable;
char * dataPath;
};
struct app app = { 0 };
@@ -37,7 +38,13 @@ struct app app = { 0 };
int main(int argc, char * argv[])
{
app.executable = argv[0];
struct passwd * pw = getpwuid(getuid());
alloc_sprintf(&app.dataPath, "%s/", pw->pw_dir);
int result = app_main(argc, argv);
free(app.dataPath);
return result;
}
@@ -56,4 +63,9 @@ bool app_init()
const char * os_getExecutable()
{
return app.executable;
}
}
const char * os_getDataPath()
{
return app.dataPath;
}

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,12 +26,26 @@ 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>
#include "dxgi_extra.h"
typedef enum _D3DKMT_SCHEDULINGPRIORITYCLASS
{
D3DKMT_SCHEDULINGPRIORITYCLASS_IDLE,
D3DKMT_SCHEDULINGPRIORITYCLASS_BELOW_NORMAL,
D3DKMT_SCHEDULINGPRIORITYCLASS_NORMAL,
D3DKMT_SCHEDULINGPRIORITYCLASS_ABOVE_NORMAL,
D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH,
D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME
}
D3DKMT_SCHEDULINGPRIORITYCLASS;
typedef NTSTATUS WINAPI (*PD3DKMTSetProcessSchedulingPriorityClass)(HANDLE, D3DKMT_SCHEDULINGPRIORITYCLASS);
#define LOCKED(x) INTERLOCKED_SECTION(this->deviceContextLock, x)
enum TextureState
@@ -43,7 +57,7 @@ enum TextureState
typedef struct Texture
{
enum TextureState state;
volatile enum TextureState state;
ID3D11Texture2D * tex;
D3D11_MAPPED_SUBRESOURCE map;
}
@@ -70,7 +84,7 @@ struct iface
Texture * texture;
int texRIndex;
int texWIndex;
volatile int texReady;
atomic_int texReady;
bool needsRelease;
CaptureGetPointerBuffer getPointerBufferFn;
@@ -130,9 +144,9 @@ static void dxgi_initOptions()
{
.module = "dxgi",
.name = "useAcquireLock",
.description = "Enable locking around `AcquireFrame` (use if freezing, may lower performance)",
.description = "Enable locking around `AcquireFrame` (EXPERIMENTAL, leave enabled if you're not sure!)",
.type = OPTION_TYPE_BOOL,
.value.x_bool = false
.value.x_bool = true
},
{0}
};
@@ -190,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
@@ -216,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))
@@ -385,6 +399,25 @@ static bool dxgi_init()
// bump up our priority
{
HMODULE gdi32 = GetModuleHandleA("GDI32");
if (gdi32)
{
PD3DKMTSetProcessSchedulingPriorityClass fn =
(PD3DKMTSetProcessSchedulingPriorityClass)GetProcAddress(gdi32, "D3DKMTSetProcessSchedulingPriorityClass");
if (fn)
{
status = fn(GetCurrentProcess(), D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME);
if (FAILED(status))
{
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");
}
}
}
IDXGIDevice * dxgi;
status = ID3D11Device_QueryInterface(this->device, &IID_IDXGIDevice, (void **)&dxgi);
if (FAILED(status))
@@ -674,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)
@@ -705,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))
{
@@ -713,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)
@@ -734,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 */
@@ -768,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)
{
@@ -821,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;
@@ -852,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;
@@ -303,6 +340,24 @@ const char * os_getExecutable()
return app.executable;
}
const char * os_getDataPath()
{
static char path[MAX_PATH] = { 0 };
if (!path[0])
{
if (!GetModuleFileName(NULL, path, MAX_PATH))
return NULL;
char *p = strrchr(path, '\\');
if (!p)
return NULL;
++p;
*p = '\0';
}
return path;
}
HWND os_getMessageWnd()
{
return app.messageWnd;

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

@@ -38,6 +38,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <stdlib.h>
#include <string.h>
#define CONFIG_FILE "looking-glass-host.ini"
#define ALIGN_DN(x) ((uintptr_t)(x) & ~0x7F)
#define ALIGN_UP(x) ALIGN_DN(x + 0x7F)
@@ -60,12 +62,22 @@ 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;
PLGMPHostQueue pointerQueue;
PLGMPMemory pointerMemory[LGMP_Q_POINTER_LEN];
LG_Lock pointerLock;
CapturePointer pointerInfo;
PLGMPMemory pointerShape;
bool pointerShapeValid;
unsigned int pointerIndex;
@@ -77,8 +89,7 @@ struct app
CaptureInterface * iface;
bool running;
bool reinit;
enum AppState state;
LGTimer * lgmpTimer;
LGThread * frameThread;
};
@@ -91,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;
}
@@ -107,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)
@@ -124,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;
}
@@ -201,13 +212,7 @@ static int frameThread(void * opaque)
bool startThreads()
{
app.running = true;
if (!lgCreateTimer(100, lgmpTimer, NULL, &app.lgmpTimer))
{
DEBUG_ERROR("Failed to create the LGMP timer");
return false;
}
app.state = APP_STATE_RUNNING;
if (!lgCreateThread("FrameThread", frameThread, NULL, &app.frameThread))
{
DEBUG_ERROR("Failed to create the frame thread");
@@ -221,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))
{
@@ -231,18 +237,19 @@ bool stopThreads()
}
app.frameThread = NULL;
if (app.lgmpTimer)
{
lgTimerDestroy(app.lgmpTimer);
app.lgmpTimer = NULL;
}
return ok;
}
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)
@@ -256,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)
{
DEBUG_INFO("pending");
if (!app.running)
usleep(1);
if (app.state == APP_STATE_RUNNING)
return false;
}
@@ -290,14 +299,12 @@ bool captureGetPointerBuffer(void ** data, uint32_t * size)
return true;
}
void capturePostPointerBuffer(CapturePointer pointer)
static void sendPointer(bool newClient)
{
PLGMPMemory mem;
const bool newClient = lgmpHostQueueNewSubs(app.pointerQueue) > 0;
if (pointer.shapeUpdate || newClient)
if (app.pointerInfo.shapeUpdate || newClient)
{
if (pointer.shapeUpdate)
if (!newClient)
{
// swap the latest shape buffer out of rotation
PLGMPMemory tmp = app.pointerShape;
@@ -309,32 +316,32 @@ void capturePostPointerBuffer(CapturePointer pointer)
mem = app.pointerShape;
}
else
{
mem = app.pointerMemory[app.pointerIndex];
if (++app.pointerIndex == LGMP_Q_POINTER_LEN)
app.pointerIndex = 0;
}
if (++app.pointerIndex == LGMP_Q_POINTER_LEN)
app.pointerIndex = 0;
uint32_t flags = 0;
KVMFRCursor *cursor = lgmpHostMemPtr(mem);
if (pointer.positionUpdate)
if (app.pointerInfo.positionUpdate || newClient)
{
flags |= CURSOR_FLAG_POSITION;
cursor->x = pointer.x;
cursor->y = pointer.y;
cursor->x = app.pointerInfo.x;
cursor->y = app.pointerInfo.y;
}
if (pointer.visible)
if (app.pointerInfo.visible)
flags |= CURSOR_FLAG_VISIBLE;
if (pointer.shapeUpdate)
if (app.pointerInfo.shapeUpdate)
{
// remember which slot has the latest shape
cursor->width = pointer.width;
cursor->height = pointer.height;
cursor->pitch = pointer.pitch;
switch(pointer.format)
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;
switch(app.pointerInfo.format)
{
case CAPTURE_FMT_COLOR : cursor->type = CURSOR_TYPE_COLOR ; break;
case CAPTURE_FMT_MONO : cursor->type = CURSOR_TYPE_MONOCHROME ; break;
@@ -348,7 +355,7 @@ void capturePostPointerBuffer(CapturePointer pointer)
app.pointerShapeValid = true;
}
if ((pointer.shapeUpdate || newClient) && app.pointerShapeValid)
if ((app.pointerInfo.shapeUpdate || newClient) && app.pointerShapeValid)
flags |= CURSOR_FLAG_SHAPE;
LGMP_STATUS status;
@@ -361,10 +368,31 @@ void capturePostPointerBuffer(CapturePointer pointer)
}
DEBUG_ERROR("lgmpHostQueuePost Failed (Pointer): %s", lgmpStatusString(status));
return;
break;
}
}
void capturePostPointerBuffer(CapturePointer pointer)
{
LG_LOCK(app.pointerLock);
int x = app.pointerInfo.x;
int y = app.pointerInfo.y;
memcpy(&app.pointerInfo, &pointer, sizeof(CapturePointer));
/* if there was not a position update, restore the x & y */
if (!pointer.positionUpdate)
{
app.pointerInfo.x = x;
app.pointerInfo.y = y;
}
sendPointer(false);
LG_UNLOCK(app.pointerLock);
}
// this is called from the platform specific startup routine
int app_main(int argc, char * argv[])
{
@@ -379,7 +407,22 @@ int app_main(int argc, char * argv[])
CaptureInterfaces[i]->initOptions();
// try load values from a config file
option_load("looking-glass-host.ini");
const char * dataPath = os_getDataPath();
if (!dataPath)
{
option_free();
DEBUG_ERROR("Failed to get the application's data path");
return -1;
}
const size_t len = strlen(dataPath) + sizeof(CONFIG_FILE) + 1;
char configFile[len];
snprintf(configFile, sizeof(configFile), "%s%s", dataPath, CONFIG_FILE);
DEBUG_INFO("Looking for configuration file at: %s", configFile);
if (option_load(configFile))
DEBUG_INFO("Configuration file loaded");
else
DEBUG_INFO("Configuration file not found or invalid");
// parse the command line arguments
if (!option_parse(argc, argv))
@@ -414,9 +457,10 @@ int app_main(int argc, char * argv[])
DEBUG_INFO("Max Pointer Size : %u KiB", (unsigned int)MAX_POINTER_SIZE / 1024);
DEBUG_INFO("KVMFR Version : %u", KVMFR_VERSION);
KVMFR udata = {
const KVMFR udata = {
.magic = KVMFR_MAGIC,
.version = KVMFR_VERSION
.version = KVMFR_VERSION,
.hostver = BUILD_VERSION
};
LGMP_STATUS status;
@@ -446,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;
@@ -496,50 +541,87 @@ int app_main(int argc, char * argv[])
goto fail;
}
DEBUG_INFO("Using : %s", iface->getName());
app.state = APP_STATE_RUNNING;
app.iface = iface;
if (!captureStart())
LG_LOCK_INIT(app.pointerLock);
if (!lgCreateTimer(100, lgmpTimer, NULL, &app.lgmpTimer))
{
exitcode = -1;
goto exit;
DEBUG_ERROR("Failed to create the LGMP timer");
goto fail;
}
while(app.running)
while(app.state != APP_STATE_SHUTDOWN)
{
if (app.reinit && !captureRestart())
if(lgmpHostQueueHasSubs(app.pointerQueue) ||
lgmpHostQueueHasSubs(app.frameQueue))
{
exitcode = -1;
goto exit;
if (!captureStart())
{
exitcode = -1;
goto exit;
}
}
app.reinit = false;
switch(iface->capture())
else
{
case CAPTURE_RESULT_OK:
break;
usleep(100000);
continue;
}
case CAPTURE_RESULT_TIMEOUT:
continue;
case CAPTURE_RESULT_REINIT:
while(app.state != APP_STATE_SHUTDOWN && (
lgmpHostQueueHasSubs(app.pointerQueue) ||
lgmpHostQueueHasSubs(app.frameQueue)))
{
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:
stopThreads();
exit:
lgTimerDestroy(app.lgmpTimer);
LG_LOCK_FREE(app.pointerLock);
iface->deinit();
iface->free();
@@ -558,5 +640,5 @@ fail:
void app_quit()
{
app.running = false;
app.state = APP_STATE_SHUTDOWN;
}

View File

@@ -1,3 +1,5 @@
#define _GNU_SOURCE //needed for pthread_setname_np
#include <obs/obs-module.h>
#include <obs/util/threading.h>
@@ -193,6 +195,7 @@ static void lgUpdate(void * data, obs_data_t * settings)
this->state = STATE_STARTING;
pthread_create(&this->frameThread, NULL, frameThread, this);
pthread_setname_np(this->frameThread, "LGFrameThread");
}
static void lgVideoTick(void * data, float seconds)
@@ -212,6 +215,16 @@ static void lgVideoTick(void * data, float seconds)
return;
}
if ((status = lgmpClientAdvanceToLast(this->frameQueue)) != LGMP_OK)
{
if (status != LGMP_ERR_QUEUE_EMPTY)
{
os_sem_post(this->frameSem);
printf("lgmpClientAdvanceToLast: %s\n", lgmpStatusString(status));
return;
}
}
if ((status = lgmpClientProcess(this->frameQueue, &msg)) != LGMP_OK)
{
if (status == LGMP_ERR_QUEUE_EMPTY)