mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-05-05 06:57:53 +00:00
Compare commits
82 Commits
B2-rc2
...
Release/B2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76710ef201 | ||
|
|
e20c8a5cc7 | ||
|
|
4f4d2dbf42 | ||
|
|
8692e9af80 | ||
|
|
7d2b39058c | ||
|
|
6927dbecd2 | ||
|
|
f9b6dcc986 | ||
|
|
5c912e3c27 | ||
|
|
7e362050f7 | ||
|
|
10fbdeb294 | ||
|
|
72d70e8322 | ||
|
|
c66a339bbc | ||
|
|
1c7961daeb | ||
|
|
cdc3384883 | ||
|
|
969effedde | ||
|
|
dc4d1d49fa | ||
|
|
4e1f947a09 | ||
|
|
15d1a74291 | ||
|
|
7dba6b9b08 | ||
|
|
a5ad531004 | ||
|
|
c119b3dcca | ||
|
|
e2f2437ef4 | ||
|
|
b2980fea63 | ||
|
|
2b518690b8 | ||
|
|
92aca75792 | ||
|
|
64fdb8b7bb | ||
|
|
431ae3fc55 | ||
|
|
380b5df9f9 | ||
|
|
c7330167cf | ||
|
|
ca02e1aba9 | ||
|
|
ca4b1f5592 | ||
|
|
0cf1e27709 | ||
|
|
045932ce77 | ||
|
|
bf5481446b | ||
|
|
e3f97e384b | ||
|
|
76e119f8ad | ||
|
|
bfb12c74fb | ||
|
|
fa50b7824c | ||
|
|
da8b2d0cec | ||
|
|
74649ddb96 | ||
|
|
4619ddef5d | ||
|
|
ea74ee6e25 | ||
|
|
ecd73aa670 | ||
|
|
10d9678b3d | ||
|
|
e08d3afdbc | ||
|
|
9a6b598438 | ||
|
|
d9a80b16f0 | ||
|
|
90d0cd873d | ||
|
|
82e0b7b6ab | ||
|
|
2e1b0f2550 | ||
|
|
3302d353cf | ||
|
|
1899d9f1da | ||
|
|
fb9b772db0 | ||
|
|
302b988524 | ||
|
|
19c2fe9b5e | ||
|
|
88d25ee98c | ||
|
|
0f2ecdf5f1 | ||
|
|
3511fb8d59 | ||
|
|
1d6d640b6e | ||
|
|
977d7b277d | ||
|
|
be7820303f | ||
|
|
43503222c7 | ||
|
|
85b8c12abf | ||
|
|
7af053497e | ||
|
|
9e3a42cb62 | ||
|
|
aa32c5ffad | ||
|
|
62d1bd1ea2 | ||
|
|
2329e993ee | ||
|
|
da655b86c3 | ||
|
|
c5ff8bd4ce | ||
|
|
06aee158de | ||
|
|
bd42445ea7 | ||
|
|
ede96fa486 | ||
|
|
67dec216d2 | ||
|
|
fcbdf7ba4f | ||
|
|
e8c949c1e7 | ||
|
|
28c93ef5ac | ||
|
|
d7921c5d5f | ||
|
|
6d296f2b44 | ||
|
|
553e2830bb | ||
|
|
667ab981ba | ||
|
|
bc7871f630 |
74
.github/issue_template.md
vendored
74
.github/issue_template.md
vendored
@@ -1,11 +1,69 @@
|
|||||||
### Required information
|
### Issues are for Bug Reports and Feature Requests Only!
|
||||||
|
|
||||||
Host CPU:
|
If you are looking for help or support please use one of the following methods
|
||||||
Host GPU:
|
|
||||||
Guest GPU:
|
|
||||||
Host Kernel version:
|
|
||||||
Host QEMU version:
|
|
||||||
|
|
||||||
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
|
||||||
|
```
|
||||||
|
|||||||
15
README.md
15
README.md
@@ -4,6 +4,7 @@ An extremely low latency KVMFR (KVM FrameRelay) implementation for guests with
|
|||||||
VGA PCI Passthrough.
|
VGA PCI Passthrough.
|
||||||
|
|
||||||
* Project Website: https://looking-glass.hostfission.com
|
* Project Website: https://looking-glass.hostfission.com
|
||||||
|
* Getting Started: https://looking-glass.hostfission.com/wiki/Installation
|
||||||
|
|
||||||
## Donations
|
## Donations
|
||||||
|
|
||||||
@@ -23,18 +24,20 @@ support me directly using the following platforms.
|
|||||||
|
|
||||||
** IMPORTANT **
|
** IMPORTANT **
|
||||||
This project contains submodules that must be checked out if building from the
|
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
|
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)
|
* [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)
|
* [module/README.md](module/README.md)
|
||||||
|
|
||||||
## Obtaining and using Looking Glass
|
|
||||||
|
|
||||||
Please see https://looking-glass.hostfission.com/wiki/
|
|
||||||
|
|
||||||
## Latest Version
|
## Latest Version
|
||||||
|
|
||||||
If you would like to use the latest bleeding edge version of Looking Glass please
|
If you would like to use the latest bleeding edge version of Looking Glass please
|
||||||
|
|||||||
@@ -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:clipboardToVM | | yes | Allow the clipboard to be syncronized TO the VM |
|
||||||
| spice:clipboardToLocal | | yes | Allow the clipboard to be syncronized FROM 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:scaleCursor | -j | yes | Scale cursor input position to screen size when up/down scaled |
|
||||||
|
| spice:captureOnStart | | no | Capture mouse and keyboard on start |
|
||||||
|------------------------------------------------------------------------------------------------------------------|
|
|------------------------------------------------------------------------------------------------------------------|
|
||||||
|
|
||||||
|--------------------------------------------------------------------------|
|
|--------------------------------------------------------------------------|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
|||||||
(x)->create && \
|
(x)->create && \
|
||||||
(x)->initialize && \
|
(x)->initialize && \
|
||||||
(x)->deinitialize && \
|
(x)->deinitialize && \
|
||||||
|
(x)->on_restart && \
|
||||||
(x)->on_resize && \
|
(x)->on_resize && \
|
||||||
(x)->on_mouse_shape && \
|
(x)->on_mouse_shape && \
|
||||||
(x)->on_mouse_event && \
|
(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_RendererCreate )(void ** opaque, const LG_RendererParams params);
|
||||||
typedef bool (* LG_RendererInitialize )(void * opaque, Uint32 * sdlFlags);
|
typedef bool (* LG_RendererInitialize )(void * opaque, Uint32 * sdlFlags);
|
||||||
typedef void (* LG_RendererDeInitialize)(void * opaque);
|
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 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_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);
|
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_RendererCreate create;
|
||||||
LG_RendererInitialize initialize;
|
LG_RendererInitialize initialize;
|
||||||
LG_RendererDeInitialize deinitialize;
|
LG_RendererDeInitialize deinitialize;
|
||||||
|
LG_RendererOnRestart on_restart;
|
||||||
LG_RendererOnResize on_resize;
|
LG_RendererOnResize on_resize;
|
||||||
LG_RendererOnMouseShape on_mouse_shape;
|
LG_RendererOnMouseShape on_mouse_shape;
|
||||||
LG_RendererOnMouseEvent on_mouse_event;
|
LG_RendererOnMouseEvent on_mouse_event;
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ struct Inst
|
|||||||
EGL_Alert * alert; // the alert display
|
EGL_Alert * alert; // the alert display
|
||||||
|
|
||||||
LG_RendererFormat format;
|
LG_RendererFormat format;
|
||||||
|
bool start;
|
||||||
uint64_t waitFadeTime;
|
uint64_t waitFadeTime;
|
||||||
bool waitDone;
|
bool waitDone;
|
||||||
|
|
||||||
@@ -225,6 +226,15 @@ void egl_deinitialize(void * opaque)
|
|||||||
free(this);
|
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)
|
void egl_on_resize(void * opaque, const int width, const int height, const LG_RendererRect destRect)
|
||||||
{
|
{
|
||||||
struct Inst * this = (struct Inst *)opaque;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this->start = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -532,7 +543,10 @@ bool egl_render(void * opaque, SDL_Window * window)
|
|||||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
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)
|
if (!this->waitFadeTime)
|
||||||
this->waitFadeTime = microtime() + SPLASH_FADE_TIME;
|
this->waitFadeTime = microtime() + SPLASH_FADE_TIME;
|
||||||
@@ -559,6 +573,11 @@ bool egl_render(void * opaque, SDL_Window * window)
|
|||||||
if (!this->waitDone)
|
if (!this->waitDone)
|
||||||
egl_splash_render(this->splash, a, this->splashRatio);
|
egl_splash_render(this->splash, a, this->splashRatio);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!this->start)
|
||||||
|
egl_splash_render(this->splash, 1.0f, this->splashRatio);
|
||||||
|
}
|
||||||
|
|
||||||
if (this->showAlert)
|
if (this->showAlert)
|
||||||
{
|
{
|
||||||
@@ -595,6 +614,7 @@ struct LG_Renderer LGR_EGL =
|
|||||||
.create = egl_create,
|
.create = egl_create,
|
||||||
.initialize = egl_initialize,
|
.initialize = egl_initialize,
|
||||||
.deinitialize = egl_deinitialize,
|
.deinitialize = egl_deinitialize,
|
||||||
|
.on_restart = egl_on_restart,
|
||||||
.on_resize = egl_on_resize,
|
.on_resize = egl_on_resize,
|
||||||
.on_mouse_shape = egl_on_mouse_shape,
|
.on_mouse_shape = egl_on_mouse_shape,
|
||||||
.on_mouse_event = egl_on_mouse_event,
|
.on_mouse_event = egl_on_mouse_event,
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ struct EGL_FPS
|
|||||||
EGL_Model * model;
|
EGL_Model * model;
|
||||||
|
|
||||||
bool ready;
|
bool ready;
|
||||||
|
int iwidth, iheight;
|
||||||
float width, height;
|
float width, height;
|
||||||
|
|
||||||
// uniforms
|
// uniforms
|
||||||
@@ -144,6 +145,13 @@ void egl_fps_update(EGL_FPS * fps, const float avgFPS, const float renderFPS)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(
|
egl_texture_setup(
|
||||||
fps->texture,
|
fps->texture,
|
||||||
EGL_PF_BGRA,
|
EGL_PF_BGRA,
|
||||||
@@ -152,6 +160,7 @@ void egl_fps_update(EGL_FPS * fps, const float avgFPS, const float renderFPS)
|
|||||||
bmp->width * bmp->bpp,
|
bmp->width * bmp->bpp,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
egl_texture_update
|
egl_texture_update
|
||||||
(
|
(
|
||||||
@@ -159,10 +168,7 @@ void egl_fps_update(EGL_FPS * fps, const float avgFPS, const float renderFPS)
|
|||||||
bmp->pixels
|
bmp->pixels
|
||||||
);
|
);
|
||||||
|
|
||||||
fps->width = bmp->width;
|
|
||||||
fps->height = bmp->height;
|
|
||||||
fps->ready = true;
|
fps->ready = true;
|
||||||
|
|
||||||
fps->font->release(fps->fontObj, bmp);
|
fps->font->release(fps->fontObj, bmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
|||||||
|
|
||||||
#include <SDL2/SDL_egl.h>
|
#include <SDL2/SDL_egl.h>
|
||||||
|
|
||||||
#define TEXTURE_COUNT 3
|
/* this must be a multiple of 2 */
|
||||||
|
#define TEXTURE_COUNT 2
|
||||||
|
|
||||||
struct Tex
|
struct Tex
|
||||||
{
|
{
|
||||||
@@ -41,19 +42,9 @@ struct Tex
|
|||||||
GLsync sync;
|
GLsync sync;
|
||||||
};
|
};
|
||||||
|
|
||||||
union TexState
|
struct TexState
|
||||||
{
|
{
|
||||||
_Atomic(uint32_t) v;
|
_Atomic(uint8_t) w, u, s, d;
|
||||||
struct
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* w = write
|
|
||||||
* u = upload
|
|
||||||
* s = schedule
|
|
||||||
* d = display
|
|
||||||
*/
|
|
||||||
_Atomic(int8_t) w, u, s, d;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct EGL_Texture
|
struct EGL_Texture
|
||||||
@@ -73,7 +64,8 @@ struct EGL_Texture
|
|||||||
GLenum dataType;
|
GLenum dataType;
|
||||||
size_t pboBufferSize;
|
size_t pboBufferSize;
|
||||||
|
|
||||||
union TexState state;
|
struct TexState state;
|
||||||
|
int textureCount;
|
||||||
struct Tex tex[TEXTURE_COUNT];
|
struct Tex tex[TEXTURE_COUNT];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -98,7 +90,7 @@ void egl_texture_free(EGL_Texture ** texture)
|
|||||||
if ((*texture)->planeCount > 0)
|
if ((*texture)->planeCount > 0)
|
||||||
glDeleteSamplers((*texture)->planeCount, (*texture)->samplers);
|
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];
|
struct Tex * t = &(*texture)->tex[i];
|
||||||
if (t->hasPBO)
|
if (t->hasPBO)
|
||||||
@@ -123,10 +115,7 @@ void egl_texture_free(EGL_Texture ** texture)
|
|||||||
*texture = NULL;
|
*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);
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[i].pbo);
|
||||||
texture->tex[i].map = glMapBufferRange(
|
texture->tex[i].map = glMapBufferRange(
|
||||||
@@ -137,42 +126,57 @@ static bool egl_texture_map(EGL_Texture * texture)
|
|||||||
GL_MAP_UNSYNCHRONIZED_BIT |
|
GL_MAP_UNSYNCHRONIZED_BIT |
|
||||||
GL_MAP_INVALIDATE_BUFFER_BIT
|
GL_MAP_INVALIDATE_BUFFER_BIT
|
||||||
);
|
);
|
||||||
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||||
|
|
||||||
if (!texture->tex[i].map)
|
if (!texture->tex[i].map)
|
||||||
{
|
{
|
||||||
EGL_ERROR("glMapBufferRange failed for %d of %lu bytes", i, texture->pboBufferSize);
|
EGL_ERROR("glMapBufferRange failed for %d of %lu bytes", i, texture->pboBufferSize);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
||||||
return true;
|
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)
|
if (!texture->tex[i].map)
|
||||||
continue;
|
return;
|
||||||
|
|
||||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[i].pbo);
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[i].pbo);
|
||||||
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
|
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
|
||||||
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||||
texture->tex[i].map = NULL;
|
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)
|
bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_t width, size_t height, size_t stride, bool streaming)
|
||||||
{
|
{
|
||||||
int planeCount;
|
int planeCount;
|
||||||
|
|
||||||
|
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->pixFmt = pixFmt;
|
||||||
texture->width = width;
|
texture->width = width;
|
||||||
texture->height = height;
|
texture->height = height;
|
||||||
texture->stride = stride;
|
texture->stride = stride;
|
||||||
texture->streaming = streaming;
|
texture->streaming = streaming;
|
||||||
|
texture->textureCount = streaming ? TEXTURE_COUNT : 1;
|
||||||
texture->ready = false;
|
texture->ready = false;
|
||||||
atomic_store_explicit(&texture->state.v, 0, memory_order_relaxed);
|
|
||||||
|
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)
|
switch(pixFmt)
|
||||||
{
|
{
|
||||||
@@ -245,7 +249,7 @@ bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_
|
|||||||
if (texture->planeCount > 0)
|
if (texture->planeCount > 0)
|
||||||
glDeleteSamplers(texture->planeCount, texture->samplers);
|
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)
|
if (texture->planeCount > 0)
|
||||||
glDeleteTextures(texture->planeCount, texture->tex[i].t);
|
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;
|
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)
|
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);
|
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->textureCount; ++i)
|
||||||
for(int i = 0; i < TEXTURE_COUNT; ++i)
|
|
||||||
{
|
{
|
||||||
if (texture->tex[i].hasPBO)
|
|
||||||
glDeleteBuffers(1, &texture->tex[i].pbo);
|
|
||||||
|
|
||||||
glGenBuffers(1, &texture->tex[i].pbo);
|
glGenBuffers(1, &texture->tex[i].pbo);
|
||||||
texture->tex[i].hasPBO = true;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,23 +316,25 @@ bool egl_texture_update(EGL_Texture * texture, const uint8_t * buffer)
|
|||||||
{
|
{
|
||||||
if (texture->streaming)
|
if (texture->streaming)
|
||||||
{
|
{
|
||||||
union TexState s;
|
const uint8_t sw =
|
||||||
s.v = atomic_load_explicit(&texture->state.v, memory_order_acquire);
|
atomic_load_explicit(&texture->state.w, memory_order_acquire);
|
||||||
|
|
||||||
const uint8_t next = (s.w + 1) % TEXTURE_COUNT;
|
if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == (uint8_t)(sw + 1))
|
||||||
if (next == s.u)
|
|
||||||
{
|
{
|
||||||
egl_warn_slow();
|
egl_warn_slow();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(texture->tex[s.w].map, buffer, texture->pboBufferSize);
|
const uint8_t t = sw % TEXTURE_COUNT;
|
||||||
atomic_store_explicit(&texture->state.w, next, memory_order_release);
|
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
|
else
|
||||||
{
|
{
|
||||||
/* Non streaming, this is NOT thread safe */
|
|
||||||
|
|
||||||
for(int p = 0; p < texture->planeCount; ++p)
|
for(int p = 0; p < texture->planeCount; ++p)
|
||||||
{
|
{
|
||||||
glBindTexture(GL_TEXTURE_2D, texture->tex[0].t[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)
|
if (!texture->streaming)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
union TexState s;
|
const uint8_t sw =
|
||||||
s.v = atomic_load_explicit(&texture->state.v, memory_order_acquire);
|
atomic_load_explicit(&texture->state.w, memory_order_acquire);
|
||||||
|
|
||||||
const uint8_t next = (s.w + 1) % TEXTURE_COUNT;
|
if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == (uint8_t)(sw + 1))
|
||||||
if (next == s.u)
|
|
||||||
{
|
{
|
||||||
egl_warn_slow();
|
egl_warn_slow();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const uint8_t t = sw % TEXTURE_COUNT;
|
||||||
|
if (!egl_texture_map(texture, t))
|
||||||
|
return EGL_TEX_STATUS_ERROR;
|
||||||
|
|
||||||
framebuffer_read(
|
framebuffer_read(
|
||||||
frame,
|
frame,
|
||||||
texture->tex[s.w].map,
|
texture->tex[t].map,
|
||||||
texture->stride,
|
texture->stride,
|
||||||
texture->height,
|
texture->height,
|
||||||
texture->width,
|
texture->width,
|
||||||
@@ -372,7 +375,9 @@ bool egl_texture_update_from_frame(EGL_Texture * texture, const FrameBuffer * fr
|
|||||||
texture->stride
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,62 +386,63 @@ enum EGL_TexStatus egl_texture_process(EGL_Texture * texture)
|
|||||||
if (!texture->streaming)
|
if (!texture->streaming)
|
||||||
return EGL_TEX_STATUS_OK;
|
return EGL_TEX_STATUS_OK;
|
||||||
|
|
||||||
union TexState s;
|
const uint8_t su =
|
||||||
s.v = atomic_load_explicit(&texture->state.v, memory_order_acquire);
|
atomic_load_explicit(&texture->state.u, memory_order_acquire);
|
||||||
|
|
||||||
const uint8_t nextu = (s.u + 1) % TEXTURE_COUNT;
|
const uint8_t nextu = su + 1;
|
||||||
if (s.u == s.w || nextu == s.s || nextu == s.d)
|
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;
|
return texture->ready ? EGL_TEX_STATUS_OK : EGL_TEX_STATUS_NOTREADY;
|
||||||
|
|
||||||
/* update the texture */
|
/* update the texture */
|
||||||
egl_texture_unmap(texture);
|
const uint8_t t = su % TEXTURE_COUNT;
|
||||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[s.u].pbo);
|
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[t].pbo);
|
||||||
for(int p = 0; p < texture->planeCount; ++p)
|
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]);
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, texture->planes[p][2]);
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture->planes[p][0], texture->planes[p][1],
|
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture->planes[p][0], texture->planes[p][1],
|
||||||
texture->format, texture->dataType, (const void *)texture->offsets[p]);
|
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 */
|
/* create a fence to prevent usage before the update is complete */
|
||||||
texture->tex[s.u].sync =
|
texture->tex[t].sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||||
glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
|
||||||
|
|
||||||
/* we must flush to ensure the sync is in the command buffer */
|
/* we must flush to ensure the sync is in the command buffer */
|
||||||
glFlush();
|
glFlush();
|
||||||
|
|
||||||
atomic_store_explicit(&texture->state.u, nextu, memory_order_release);
|
|
||||||
|
|
||||||
/* remap the for the next update */
|
|
||||||
egl_texture_map(texture);
|
|
||||||
|
|
||||||
texture->ready = true;
|
texture->ready = true;
|
||||||
|
atomic_fetch_add_explicit(&texture->state.u, 1, memory_order_release);
|
||||||
|
|
||||||
return EGL_TEX_STATUS_OK;
|
return EGL_TEX_STATUS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum EGL_TexStatus egl_texture_bind(EGL_Texture * texture)
|
enum EGL_TexStatus egl_texture_bind(EGL_Texture * texture)
|
||||||
{
|
{
|
||||||
union TexState s;
|
uint8_t ss = atomic_load_explicit(&texture->state.s, memory_order_acquire);
|
||||||
s.v = atomic_load_explicit(&texture->state.v, memory_order_acquire);
|
uint8_t sd = atomic_load_explicit(&texture->state.d, memory_order_acquire);
|
||||||
|
|
||||||
if (texture->streaming)
|
if (texture->streaming)
|
||||||
{
|
{
|
||||||
if (!texture->ready)
|
if (!texture->ready)
|
||||||
return EGL_TEX_STATUS_NOTREADY;
|
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_ALREADY_SIGNALED:
|
||||||
case GL_CONDITION_SATISFIED:
|
case GL_CONDITION_SATISFIED:
|
||||||
glDeleteSync(texture->tex[s.s].sync);
|
glDeleteSync(texture->tex[t].sync);
|
||||||
texture->tex[s.s].sync = 0;
|
texture->tex[t].sync = 0;
|
||||||
|
|
||||||
s.s = (s.s + 1) % TEXTURE_COUNT;
|
ss = atomic_fetch_add_explicit(&texture->state.s, 1,
|
||||||
atomic_store_explicit(&texture->state.s, s.s, memory_order_release);
|
memory_order_release) + 1;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GL_TIMEOUT_EXPIRED:
|
case GL_TIMEOUT_EXPIRED:
|
||||||
@@ -444,25 +450,23 @@ enum EGL_TexStatus egl_texture_bind(EGL_Texture * texture)
|
|||||||
|
|
||||||
case GL_WAIT_FAILED:
|
case GL_WAIT_FAILED:
|
||||||
case GL_INVALID_VALUE:
|
case GL_INVALID_VALUE:
|
||||||
glDeleteSync(texture->tex[s.s].sync);
|
glDeleteSync(texture->tex[t].sync);
|
||||||
texture->tex[s.s].sync = 0;
|
texture->tex[t].sync = 0;
|
||||||
EGL_ERROR("glClientWaitSync failed");
|
EGL_ERROR("glClientWaitSync failed");
|
||||||
return EGL_TEX_STATUS_ERROR;
|
return EGL_TEX_STATUS_ERROR;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const int8_t nextd = (s.d + 1) % TEXTURE_COUNT;
|
if (ss != sd && ss != (uint8_t)(sd + 1))
|
||||||
if (s.d != s.s && nextd != s.s)
|
sd = atomic_fetch_add_explicit(&texture->state.d, 1,
|
||||||
{
|
memory_order_release) + 1;
|
||||||
s.d = nextd;
|
|
||||||
atomic_store_explicit(&texture->state.d, nextd, memory_order_release);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const uint8_t t = sd % TEXTURE_COUNT;
|
||||||
for(int i = 0; i < texture->planeCount; ++i)
|
for(int i = 0; i < texture->planeCount; ++i)
|
||||||
{
|
{
|
||||||
glActiveTexture(GL_TEXTURE0 + 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]);
|
glBindSampler(i, texture->samplers[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -295,6 +295,12 @@ void opengl_deinitialize(void * opaque)
|
|||||||
free(this);
|
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)
|
void opengl_on_resize(void * opaque, const int width, const int height, const LG_RendererRect destRect)
|
||||||
{
|
{
|
||||||
struct Inst * this = (struct Inst *)opaque;
|
struct Inst * this = (struct Inst *)opaque;
|
||||||
@@ -823,6 +829,7 @@ const LG_Renderer LGR_OpenGL =
|
|||||||
.create = opengl_create,
|
.create = opengl_create,
|
||||||
.initialize = opengl_initialize,
|
.initialize = opengl_initialize,
|
||||||
.deinitialize = opengl_deinitialize,
|
.deinitialize = opengl_deinitialize,
|
||||||
|
.on_restart = opengl_on_restart,
|
||||||
.on_resize = opengl_on_resize,
|
.on_resize = opengl_on_resize,
|
||||||
.on_mouse_shape = opengl_on_mouse_shape,
|
.on_mouse_shape = opengl_on_mouse_shape,
|
||||||
.on_mouse_event = opengl_on_mouse_event,
|
.on_mouse_event = opengl_on_mouse_event,
|
||||||
|
|||||||
@@ -247,6 +247,13 @@ static struct Option options[] =
|
|||||||
.type = OPTION_TYPE_INT,
|
.type = OPTION_TYPE_INT,
|
||||||
.value.x_int = 0,
|
.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
|
// spice options
|
||||||
{
|
{
|
||||||
@@ -309,6 +316,13 @@ static struct Option options[] =
|
|||||||
.type = OPTION_TYPE_BOOL,
|
.type = OPTION_TYPE_BOOL,
|
||||||
.value.x_bool = true
|
.value.x_bool = true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.module = "spice",
|
||||||
|
.name = "captureOnStart",
|
||||||
|
.description = "Capture mouse and keyboard on start",
|
||||||
|
.type = OPTION_TYPE_BOOL,
|
||||||
|
.value.x_bool = false
|
||||||
|
},
|
||||||
{0}
|
{0}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -392,6 +406,7 @@ bool config_load(int argc, char * argv[])
|
|||||||
params.escapeKey = option_get_int ("input", "escapeKey" );
|
params.escapeKey = option_get_int ("input", "escapeKey" );
|
||||||
params.hideMouse = option_get_bool ("input", "hideCursor" );
|
params.hideMouse = option_get_bool ("input", "hideCursor" );
|
||||||
params.mouseSens = option_get_int ("input", "mouseSens" );
|
params.mouseSens = option_get_int ("input", "mouseSens" );
|
||||||
|
params.mouseRedraw = option_get_bool ("input", "mouseRedraw" );
|
||||||
|
|
||||||
params.minimizeOnFocusLoss = option_get_bool("win", "minimizeOnFocusLoss");
|
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.scaleMouseInput = option_get_bool("spice", "scaleCursor");
|
||||||
|
params.captureOnStart = option_get_bool("spice", "captureOnStart");
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <stdatomic.h>
|
||||||
|
|
||||||
#if SDL_VIDEO_DRIVER_X11_XINPUT2
|
#if SDL_VIDEO_DRIVER_X11_XINPUT2
|
||||||
// because SDL2 sucks and we need to turn it off
|
// 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 LGThread *t_frame = NULL;
|
||||||
static SDL_Cursor *cursor = NULL;
|
static SDL_Cursor *cursor = NULL;
|
||||||
|
|
||||||
|
static atomic_uint a_framesPending = 0;
|
||||||
|
|
||||||
struct AppState state;
|
struct AppState state;
|
||||||
|
|
||||||
// this structure is initialized in config.c
|
// 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))
|
if (!state.lgr->render_startup(state.lgrData, state.window))
|
||||||
{
|
{
|
||||||
state.running = false;
|
state.state = APP_STATE_SHUTDOWN;
|
||||||
|
|
||||||
/* unblock threads waiting on the condition */
|
/* unblock threads waiting on the condition */
|
||||||
lgSignalEvent(e_startup);
|
lgSignalEvent(e_startup);
|
||||||
@@ -152,26 +155,16 @@ static int renderThread(void * unused)
|
|||||||
struct timespec time;
|
struct timespec time;
|
||||||
clock_gettime(CLOCK_REALTIME, &time);
|
clock_gettime(CLOCK_REALTIME, &time);
|
||||||
|
|
||||||
while(state.running)
|
while(state.state != APP_STATE_SHUTDOWN)
|
||||||
{
|
{
|
||||||
if (state.frameTime > 0)
|
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)
|
if (++resyncCheck == 100)
|
||||||
{
|
{
|
||||||
resyncCheck = 0;
|
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)
|
if (state.lgrResize)
|
||||||
@@ -193,12 +186,12 @@ static int renderThread(void * unused)
|
|||||||
|
|
||||||
if (state.renderTime > 1e9)
|
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);
|
const float avgFPS = 1000.0f / (((float)state.renderTime / state.renderCount) / 1e6f);
|
||||||
state.lgr->update_fps(state.lgrData, avgUPS, avgFPS);
|
state.lgr->update_fps(state.lgrData, avgUPS, avgFPS);
|
||||||
|
|
||||||
|
atomic_store_explicit(&state.frameCount, 0, memory_order_release);
|
||||||
state.renderTime = 0;
|
state.renderTime = 0;
|
||||||
state.frameCount = 0;
|
|
||||||
state.renderCount = 0;
|
state.renderCount = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -214,10 +207,29 @@ static int renderThread(void * unused)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (state.frameTime > 0)
|
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)
|
if (t_cursor)
|
||||||
lgJoinThread(t_cursor, NULL);
|
lgJoinThread(t_cursor, NULL);
|
||||||
@@ -239,7 +251,7 @@ static int cursorThread(void * unused)
|
|||||||
lgWaitEvent(e_startup, TIMEOUT_INFINITE);
|
lgWaitEvent(e_startup, TIMEOUT_INFINITE);
|
||||||
|
|
||||||
// subscribe to the pointer queue
|
// subscribe to the pointer queue
|
||||||
while(state.running)
|
while(state.state == APP_STATE_RUNNING)
|
||||||
{
|
{
|
||||||
status = lgmpClientSubscribe(state.lgmp, LGMP_Q_POINTER, &queue);
|
status = lgmpClientSubscribe(state.lgmp, LGMP_Q_POINTER, &queue);
|
||||||
if (status == LGMP_OK)
|
if (status == LGMP_OK)
|
||||||
@@ -252,11 +264,11 @@ static int cursorThread(void * unused)
|
|||||||
}
|
}
|
||||||
|
|
||||||
DEBUG_ERROR("lgmpClientSubscribe Failed: %s", lgmpStatusString(status));
|
DEBUG_ERROR("lgmpClientSubscribe Failed: %s", lgmpStatusString(status));
|
||||||
state.running = false;
|
state.state = APP_STATE_SHUTDOWN;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
while(state.running)
|
while(state.state == APP_STATE_RUNNING)
|
||||||
{
|
{
|
||||||
LGMPMessage msg;
|
LGMPMessage msg;
|
||||||
if ((status = lgmpClientProcess(queue, &msg)) != LGMP_OK)
|
if ((status = lgmpClientProcess(queue, &msg)) != LGMP_OK)
|
||||||
@@ -269,18 +281,25 @@ static int cursorThread(void * unused)
|
|||||||
state.lgr->on_mouse_event
|
state.lgr->on_mouse_event
|
||||||
(
|
(
|
||||||
state.lgrData,
|
state.lgrData,
|
||||||
state.cursorVisible && state.drawCursor && state.cursorInView,
|
state.cursorVisible && state.drawCursor,
|
||||||
state.cursor.x,
|
state.cursor.x,
|
||||||
state.cursor.y
|
state.cursor.y
|
||||||
);
|
);
|
||||||
|
|
||||||
|
lgSignalEvent(e_frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
usleep(params.cursorPollInterval);
|
usleep(params.cursorPollInterval);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (status == LGMP_ERR_INVALID_SESSION)
|
||||||
|
state.state = APP_STATE_RESTART;
|
||||||
|
else
|
||||||
|
{
|
||||||
DEBUG_ERROR("lgmpClientProcess Failed: %s", lgmpStatusString(status));
|
DEBUG_ERROR("lgmpClientProcess Failed: %s", lgmpStatusString(status));
|
||||||
state.running = false;
|
state.state = APP_STATE_SHUTDOWN;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,19 +308,6 @@ static int cursorThread(void * unused)
|
|||||||
state.cursorVisible =
|
state.cursorVisible =
|
||||||
msg.udata & CURSOR_FLAG_VISIBLE;
|
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)
|
if (msg.udata & CURSOR_FLAG_SHAPE)
|
||||||
{
|
{
|
||||||
switch(cursor->type)
|
switch(cursor->type)
|
||||||
@@ -315,6 +321,9 @@ static int cursorThread(void * unused)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state.cursor.hx = cursor->hx;
|
||||||
|
state.cursor.hy = cursor->hy;
|
||||||
|
|
||||||
const uint8_t * data = (const uint8_t *)(cursor + 1);
|
const uint8_t * data = (const uint8_t *)(cursor + 1);
|
||||||
if (!state.lgr->on_mouse_shape(
|
if (!state.lgr->on_mouse_shape(
|
||||||
state.lgrData,
|
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);
|
lgmpClientMessageDone(queue);
|
||||||
state.updateCursor = false;
|
state.updateCursor = false;
|
||||||
|
|
||||||
@@ -341,10 +360,12 @@ static int cursorThread(void * unused)
|
|||||||
state.cursor.x,
|
state.cursor.x,
|
||||||
state.cursor.y
|
state.cursor.y
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (params.mouseRedraw)
|
||||||
|
lgSignalEvent(e_frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
lgmpClientUnsubscribe(&queue);
|
lgmpClientUnsubscribe(&queue);
|
||||||
state.running = false;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,11 +376,11 @@ static int frameThread(void * unused)
|
|||||||
|
|
||||||
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
|
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
|
||||||
lgWaitEvent(e_startup, TIMEOUT_INFINITE);
|
lgWaitEvent(e_startup, TIMEOUT_INFINITE);
|
||||||
if (!state.running)
|
if (state.state != APP_STATE_RUNNING)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
// subscribe to the frame queue
|
// subscribe to the frame queue
|
||||||
while(state.running)
|
while(state.state == APP_STATE_RUNNING)
|
||||||
{
|
{
|
||||||
status = lgmpClientSubscribe(state.lgmp, LGMP_Q_FRAME, &queue);
|
status = lgmpClientSubscribe(state.lgmp, LGMP_Q_FRAME, &queue);
|
||||||
if (status == LGMP_OK)
|
if (status == LGMP_OK)
|
||||||
@@ -372,11 +393,11 @@ static int frameThread(void * unused)
|
|||||||
}
|
}
|
||||||
|
|
||||||
DEBUG_ERROR("lgmpClientSubscribe Failed: %s", lgmpStatusString(status));
|
DEBUG_ERROR("lgmpClientSubscribe Failed: %s", lgmpStatusString(status));
|
||||||
state.running = false;
|
state.state = APP_STATE_SHUTDOWN;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
while(state.running)
|
while(state.state == APP_STATE_RUNNING)
|
||||||
{
|
{
|
||||||
LGMPMessage msg;
|
LGMPMessage msg;
|
||||||
if ((status = lgmpClientProcess(queue, &msg)) != LGMP_OK)
|
if ((status = lgmpClientProcess(queue, &msg)) != LGMP_OK)
|
||||||
@@ -387,7 +408,13 @@ static int frameThread(void * unused)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (status == LGMP_ERR_INVALID_SESSION)
|
||||||
|
state.state = APP_STATE_RESTART;
|
||||||
|
else
|
||||||
|
{
|
||||||
DEBUG_ERROR("lgmpClientProcess Failed: %s", lgmpStatusString(status));
|
DEBUG_ERROR("lgmpClientProcess Failed: %s", lgmpStatusString(status));
|
||||||
|
state.state = APP_STATE_SHUTDOWN;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -427,6 +454,7 @@ static int frameThread(void * unused)
|
|||||||
if (error)
|
if (error)
|
||||||
{
|
{
|
||||||
lgmpClientMessageDone(queue);
|
lgmpClientMessageDone(queue);
|
||||||
|
state.state = APP_STATE_SHUTDOWN;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,33 +473,35 @@ static int frameThread(void * unused)
|
|||||||
if (!state.lgr->on_frame_event(state.lgrData, lgrFormat, fb))
|
if (!state.lgr->on_frame_event(state.lgrData, lgrFormat, fb))
|
||||||
{
|
{
|
||||||
DEBUG_ERROR("renderer on frame event returned failure");
|
DEBUG_ERROR("renderer on frame event returned failure");
|
||||||
|
state.state = APP_STATE_SHUTDOWN;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
lgmpClientMessageDone(queue);
|
|
||||||
++state.frameCount;
|
|
||||||
|
|
||||||
|
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);
|
lgSignalEvent(e_frame);
|
||||||
|
|
||||||
|
lgmpClientMessageDone(queue);
|
||||||
}
|
}
|
||||||
|
|
||||||
lgmpClientUnsubscribe(&queue);
|
lgmpClientUnsubscribe(&queue);
|
||||||
state.running = false;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int spiceThread(void * arg)
|
int spiceThread(void * arg)
|
||||||
{
|
{
|
||||||
while(state.running)
|
while(state.state != APP_STATE_SHUTDOWN)
|
||||||
if (!spice_process(1000))
|
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");
|
DEBUG_ERROR("failed to process spice messages");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.running = false;
|
state.state = APP_STATE_SHUTDOWN;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -647,10 +677,22 @@ void spiceClipboardRequest(const SpiceDataType type)
|
|||||||
state.lgc->request(spice_type_to_clipboard_type(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 void handleMouseMoveEvent(int ex, int ey)
|
||||||
{
|
{
|
||||||
static bool wrapping = false;
|
|
||||||
static int wrapX, wrapY;
|
|
||||||
|
|
||||||
state.curLocalX = ex;
|
state.curLocalX = ex;
|
||||||
state.curLocalY = ey;
|
state.curLocalY = ey;
|
||||||
@@ -659,28 +701,23 @@ static void handleMouseMoveEvent(int ex, int ey)
|
|||||||
if (state.ignoreInput || !params.useSpiceInput)
|
if (state.ignoreInput || !params.useSpiceInput)
|
||||||
return;
|
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 (state.serverMode)
|
||||||
{
|
|
||||||
if (wrapping)
|
|
||||||
{
|
|
||||||
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 (
|
if (
|
||||||
ex < 100 || ex > state.windowW - 100 ||
|
ex < 100 || ex > state.windowW - 100 ||
|
||||||
ey < 100 || ey > state.windowH - 100)
|
ey < 100 || ey > state.windowH - 100)
|
||||||
{
|
{
|
||||||
wrapping = true;
|
warpMouse(state.windowW / 2, state.windowH / 2);
|
||||||
wrapX = ex;
|
|
||||||
wrapY = ey;
|
|
||||||
SDL_WarpMouseInWindow(state.window, state.windowW / 2, state.windowH / 2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -692,6 +729,10 @@ static void handleMouseMoveEvent(int ex, int ey)
|
|||||||
{
|
{
|
||||||
state.cursorInView = false;
|
state.cursorInView = false;
|
||||||
state.updateCursor = true;
|
state.updateCursor = true;
|
||||||
|
state.warpState = WARP_STATE_OFF;
|
||||||
|
|
||||||
|
if (params.useSpiceInput)
|
||||||
|
state.drawCursor = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -700,6 +741,9 @@ static void handleMouseMoveEvent(int ex, int ey)
|
|||||||
{
|
{
|
||||||
state.cursorInView = true;
|
state.cursorInView = true;
|
||||||
state.updateCursor = true;
|
state.updateCursor = true;
|
||||||
|
state.drawCursor = true;
|
||||||
|
if (state.warpState == WARP_STATE_ARMED)
|
||||||
|
state.warpState = WARP_STATE_ON;
|
||||||
}
|
}
|
||||||
|
|
||||||
int rx = ex - state.curLastX;
|
int rx = ex - state.curLastX;
|
||||||
@@ -739,9 +783,9 @@ static void alignMouseWithGuest()
|
|||||||
if (state.ignoreInput || !params.useSpiceInput)
|
if (state.ignoreInput || !params.useSpiceInput)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
state.curLastX = (int)round((float)state.cursor.x / state.scaleX) + state.dstRect.x;
|
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.scaleY) + state.dstRect.y;
|
state.curLastY = (int)round((float)(state.cursor.y + state.cursor.hy) / state.scaleY) + state.dstRect.y;
|
||||||
SDL_WarpMouseInWindow(state.window, state.curLastX, state.curLastY);
|
warpMouse(state.curLastX, state.curLastY);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void alignMouseWithHost()
|
static void alignMouseWithHost()
|
||||||
@@ -752,8 +796,8 @@ static void alignMouseWithHost()
|
|||||||
if (!state.haveCursorPos || state.serverMode)
|
if (!state.haveCursorPos || state.serverMode)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
state.curLastX = (int)round((float)state.cursor.x / state.scaleX) + state.dstRect.x;
|
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.scaleY) + state.dstRect.y;
|
state.curLastY = (int)round((float)(state.cursor.y + state.cursor.hy) / state.scaleY) + state.dstRect.y;
|
||||||
handleMouseMoveEvent(state.curLocalX, state.curLocalY);
|
handleMouseMoveEvent(state.curLocalX, state.curLocalY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -775,6 +819,7 @@ static void handleWindowLeave()
|
|||||||
state.drawCursor = false;
|
state.drawCursor = false;
|
||||||
state.cursorInView = false;
|
state.cursorInView = false;
|
||||||
state.updateCursor = true;
|
state.updateCursor = true;
|
||||||
|
state.warpState = WARP_STATE_OFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void handleWindowEnter()
|
static void handleWindowEnter()
|
||||||
@@ -785,6 +830,7 @@ static void handleWindowEnter()
|
|||||||
alignMouseWithHost();
|
alignMouseWithHost();
|
||||||
state.drawCursor = true;
|
state.drawCursor = true;
|
||||||
state.updateCursor = true;
|
state.updateCursor = true;
|
||||||
|
state.warpState = WARP_STATE_ARMED;
|
||||||
}
|
}
|
||||||
|
|
||||||
int eventFilter(void * userdata, SDL_Event * event)
|
int eventFilter(void * userdata, SDL_Event * event)
|
||||||
@@ -796,7 +842,7 @@ int eventFilter(void * userdata, SDL_Event * event)
|
|||||||
if (!params.ignoreQuit)
|
if (!params.ignoreQuit)
|
||||||
{
|
{
|
||||||
DEBUG_INFO("Quit event received, exiting...");
|
DEBUG_INFO("Quit event received, exiting...");
|
||||||
state.running = false;
|
state.state = APP_STATE_SHUTDOWN;
|
||||||
}
|
}
|
||||||
return 0;
|
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
|
// allow a window close event to close the application even if ignoreQuit is set
|
||||||
case SDL_WINDOWEVENT_CLOSE:
|
case SDL_WINDOWEVENT_CLOSE:
|
||||||
state.running = false;
|
state.state = APP_STATE_SHUTDOWN;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
@@ -919,7 +965,6 @@ int eventFilter(void * userdata, SDL_Event * event)
|
|||||||
if (params.useSpiceInput)
|
if (params.useSpiceInput)
|
||||||
{
|
{
|
||||||
state.serverMode = !state.serverMode;
|
state.serverMode = !state.serverMode;
|
||||||
spice_mouse_mode(state.serverMode);
|
|
||||||
SDL_SetWindowGrab(state.window, state.serverMode);
|
SDL_SetWindowGrab(state.window, state.serverMode);
|
||||||
DEBUG_INFO("Server Mode: %s", state.serverMode ? "on" : "off");
|
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"
|
state.serverMode ? "Capture Enabled" : "Capture Disabled"
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!state.serverMode)
|
if (state.serverMode)
|
||||||
|
state.warpState = WARP_STATE_ON;
|
||||||
|
else
|
||||||
alignMouseWithGuest();
|
alignMouseWithGuest();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1024,7 +1071,7 @@ void int_handler(int signal)
|
|||||||
case SIGINT:
|
case SIGINT:
|
||||||
case SIGTERM:
|
case SIGTERM:
|
||||||
DEBUG_INFO("Caught signal, shutting down...");
|
DEBUG_INFO("Caught signal, shutting down...");
|
||||||
state.running = false;
|
state.state = APP_STATE_SHUTDOWN;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1072,7 +1119,7 @@ static void toggle_input(SDL_Scancode key, void * opaque)
|
|||||||
|
|
||||||
static void quit(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)
|
static void mouse_sens_inc(SDL_Scancode key, void * opaque)
|
||||||
@@ -1155,7 +1202,7 @@ static void release_key_binds()
|
|||||||
static int lg_run()
|
static int lg_run()
|
||||||
{
|
{
|
||||||
memset(&state, 0, sizeof(state));
|
memset(&state, 0, sizeof(state));
|
||||||
state.running = true;
|
state.state = APP_STATE_RUNNING;
|
||||||
state.scaleX = 1.0f;
|
state.scaleX = 1.0f;
|
||||||
state.scaleY = 1.0f;
|
state.scaleY = 1.0f;
|
||||||
state.resizeDone = true;
|
state.resizeDone = true;
|
||||||
@@ -1218,14 +1265,15 @@ static int lg_run()
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
while(state.running && !spice_ready())
|
while(state.state != APP_STATE_SHUTDOWN && !spice_ready())
|
||||||
if (!spice_process(1000))
|
if (!spice_process(1000))
|
||||||
{
|
{
|
||||||
state.running = false;
|
state.state = APP_STATE_SHUTDOWN;
|
||||||
DEBUG_ERROR("Failed to process spice messages");
|
DEBUG_ERROR("Failed to process spice messages");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
spice_mouse_mode(true);
|
||||||
if (!lgCreateThread("spiceThread", spiceThread, NULL, &t_spice))
|
if (!lgCreateThread("spiceThread", spiceThread, NULL, &t_spice))
|
||||||
{
|
{
|
||||||
DEBUG_ERROR("spice create thread failed");
|
DEBUG_ERROR("spice create thread failed");
|
||||||
@@ -1310,13 +1358,15 @@ static int lg_run()
|
|||||||
// ensure renderer viewport is aware of the current window size
|
// ensure renderer viewport is aware of the current window size
|
||||||
updatePositionInfo();
|
updatePositionInfo();
|
||||||
|
|
||||||
// use a default of 60FPS now that frame updates are host update triggered
|
|
||||||
if (params.fpsMin == -1)
|
if (params.fpsMin == -1)
|
||||||
state.frameTime = 1e9 / 60;
|
{
|
||||||
|
// minimum 60fps to keep interactivity decent
|
||||||
|
state.frameTime = 1000000000ULL / 60ULL;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DEBUG_INFO("Using the FPS minimum from args: %d", params.fpsMin);
|
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();
|
register_key_binds();
|
||||||
@@ -1394,6 +1444,13 @@ static int lg_run()
|
|||||||
SDL_ShowCursor(SDL_DISABLE);
|
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
|
// setup the startup condition
|
||||||
if (!(e_startup = lgCreateEvent(false, 0)))
|
if (!(e_startup = lgCreateEvent(false, 0)))
|
||||||
{
|
{
|
||||||
@@ -1425,7 +1482,7 @@ static int lg_run()
|
|||||||
|
|
||||||
LGMP_STATUS status;
|
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)
|
if ((status = lgmpClientInit(state.shm.mem, state.shm.size, &state.lgmp)) == LGMP_OK)
|
||||||
break;
|
break;
|
||||||
@@ -1440,9 +1497,10 @@ static int lg_run()
|
|||||||
|
|
||||||
uint32_t udataSize;
|
uint32_t udataSize;
|
||||||
KVMFR *udata;
|
KVMFR *udata;
|
||||||
|
|
||||||
int waitCount = 0;
|
int waitCount = 0;
|
||||||
while(state.running)
|
|
||||||
|
restart:
|
||||||
|
while(state.state == APP_STATE_RUNNING)
|
||||||
{
|
{
|
||||||
if ((status = lgmpClientSessionInit(state.lgmp, &udataSize, (uint8_t **)&udata)) == LGMP_OK)
|
if ((status = lgmpClientSessionInit(state.lgmp, &udataSize, (uint8_t **)&udata)) == LGMP_OK)
|
||||||
break;
|
break;
|
||||||
@@ -1471,22 +1529,35 @@ static int lg_run()
|
|||||||
SDL_WaitEventTimeout(NULL, 1000);
|
SDL_WaitEventTimeout(NULL, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!state.running)
|
if (state.state != APP_STATE_RUNNING)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
if (udataSize != sizeof(KVMFR) ||
|
// dont show warnings again after the first startup
|
||||||
memcmp(udata->magic, KVMFR_MAGIC, sizeof(udata->magic)) != 0 ||
|
waitCount = 100;
|
||||||
udata->version != KVMFR_VERSION)
|
|
||||||
|
const bool magicMatches = memcmp(udata->magic, KVMFR_MAGIC, sizeof(udata->magic)) == 0;
|
||||||
|
if (udataSize != sizeof(KVMFR) || !magicMatches || udata->version != KVMFR_VERSION)
|
||||||
{
|
{
|
||||||
DEBUG_BREAK();
|
DEBUG_BREAK();
|
||||||
DEBUG_ERROR("The host application is not compatible with this client");
|
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("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();
|
DEBUG_BREAK();
|
||||||
return -1;
|
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))
|
if (!lgCreateThread("cursorThread", cursorThread, NULL, &t_cursor))
|
||||||
{
|
{
|
||||||
@@ -1500,14 +1571,30 @@ static int lg_run()
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
while(state.running)
|
while(state.state == APP_STATE_RUNNING)
|
||||||
{
|
{
|
||||||
if (!lgmpClientSessionValid(state.lgmp))
|
if (!lgmpClientSessionValid(state.lgmp))
|
||||||
{
|
{
|
||||||
DEBUG_WARN("Session is invalid, has the host shutdown?");
|
state.state = APP_STATE_RESTART;
|
||||||
break;
|
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;
|
return 0;
|
||||||
@@ -1515,8 +1602,7 @@ static int lg_run()
|
|||||||
|
|
||||||
static void lg_shutdown()
|
static void lg_shutdown()
|
||||||
{
|
{
|
||||||
state.running = false;
|
state.state = APP_STATE_SHUTDOWN;
|
||||||
|
|
||||||
if (t_render)
|
if (t_render)
|
||||||
{
|
{
|
||||||
lgSignalEvent(e_startup);
|
lgSignalEvent(e_startup);
|
||||||
@@ -1580,6 +1666,12 @@ static void lg_shutdown()
|
|||||||
|
|
||||||
int main(int argc, char * argv[])
|
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("Looking Glass (" BUILD_VERSION ")");
|
||||||
DEBUG_INFO("Locking Method: " LG_LOCK_MODE);
|
DEBUG_INFO("Locking Method: " LG_LOCK_MODE);
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <stdatomic.h>
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
#include "interface/app.h"
|
#include "interface/app.h"
|
||||||
@@ -28,9 +29,30 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
|||||||
#include "spice/spice.h"
|
#include "spice/spice.h"
|
||||||
#include <lgmp/client.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
|
struct AppState
|
||||||
{
|
{
|
||||||
bool running;
|
enum RunState state;
|
||||||
bool ignoreInput;
|
bool ignoreInput;
|
||||||
bool escapeActive;
|
bool escapeActive;
|
||||||
SDL_Scancode escapeAction;
|
SDL_Scancode escapeAction;
|
||||||
@@ -41,7 +63,7 @@ struct AppState
|
|||||||
int windowW, windowH;
|
int windowW, windowH;
|
||||||
SDL_Point srcSize;
|
SDL_Point srcSize;
|
||||||
LG_RendererRect dstRect;
|
LG_RendererRect dstRect;
|
||||||
SDL_Point cursor;
|
struct CursorInfo cursor;
|
||||||
bool cursorVisible;
|
bool cursorVisible;
|
||||||
|
|
||||||
bool serverMode;
|
bool serverMode;
|
||||||
@@ -59,6 +81,10 @@ struct AppState
|
|||||||
int curLocalY;
|
int curLocalY;
|
||||||
bool haveAligned;
|
bool haveAligned;
|
||||||
|
|
||||||
|
enum WarpState warpState;
|
||||||
|
int warpFromX, warpFromY;
|
||||||
|
int warpToX , warpToY;
|
||||||
|
|
||||||
const LG_Renderer * lgr;
|
const LG_Renderer * lgr;
|
||||||
void * lgrData;
|
void * lgrData;
|
||||||
bool lgrResize;
|
bool lgrResize;
|
||||||
@@ -75,7 +101,7 @@ struct AppState
|
|||||||
PLGMPClientQueue frameQueue;
|
PLGMPClientQueue frameQueue;
|
||||||
PLGMPClientQueue pointerQueue;
|
PLGMPClientQueue pointerQueue;
|
||||||
|
|
||||||
uint64_t frameTime;
|
atomic_uint_least64_t frameTime;
|
||||||
uint64_t lastFrameTime;
|
uint64_t lastFrameTime;
|
||||||
uint64_t renderTime;
|
uint64_t renderTime;
|
||||||
uint64_t frameCount;
|
uint64_t frameCount;
|
||||||
@@ -124,6 +150,7 @@ struct AppParams
|
|||||||
bool grabKeyboard;
|
bool grabKeyboard;
|
||||||
SDL_Scancode escapeKey;
|
SDL_Scancode escapeKey;
|
||||||
bool showAlerts;
|
bool showAlerts;
|
||||||
|
bool captureOnStart;
|
||||||
|
|
||||||
unsigned int cursorPollInterval;
|
unsigned int cursorPollInterval;
|
||||||
unsigned int framePollInterval;
|
unsigned int framePollInterval;
|
||||||
@@ -133,6 +160,7 @@ struct AppParams
|
|||||||
|
|
||||||
const char * windowTitle;
|
const char * windowTitle;
|
||||||
int mouseSens;
|
int mouseSens;
|
||||||
|
bool mouseRedraw;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CBRequest
|
struct CBRequest
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ include_directories(
|
|||||||
${PROJECT_SOURCE_DIR}/include
|
${PROJECT_SOURCE_DIR}/include
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_definitions(-D_GNU_SOURCE)
|
||||||
|
|
||||||
if(ENABLE_BACKTRACE)
|
if(ENABLE_BACKTRACE)
|
||||||
add_definitions(-DENABLE_BACKTRACE)
|
add_definitions(-DENABLE_BACKTRACE)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -51,12 +51,13 @@ typedef enum CursorType
|
|||||||
CursorType;
|
CursorType;
|
||||||
|
|
||||||
#define KVMFR_MAGIC "KVMFR---"
|
#define KVMFR_MAGIC "KVMFR---"
|
||||||
#define KVMFR_VERSION 1
|
#define KVMFR_VERSION 3
|
||||||
|
|
||||||
typedef struct KVMFR
|
typedef struct KVMFR
|
||||||
{
|
{
|
||||||
char magic[8];
|
char magic[8];
|
||||||
uint32_t version;
|
uint32_t version;
|
||||||
|
char hostver[32];
|
||||||
}
|
}
|
||||||
KVMFR;
|
KVMFR;
|
||||||
|
|
||||||
@@ -64,6 +65,7 @@ typedef struct KVMFRCursor
|
|||||||
{
|
{
|
||||||
int16_t x, y; // cursor x & y position
|
int16_t x, y; // cursor x & y position
|
||||||
CursorType type; // shape buffer data type
|
CursorType type; // shape buffer data type
|
||||||
|
int8_t hx, hy; // shape hotspot x & y
|
||||||
uint32_t width; // width of the shape
|
uint32_t width; // width of the shape
|
||||||
uint32_t height; // height of the shape
|
uint32_t height; // height of the shape
|
||||||
uint32_t pitch; // row length in bytes of the shape
|
uint32_t pitch; // row length in bytes of the shape
|
||||||
|
|||||||
@@ -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) > 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))
|
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_BREAK() DEBUG_PRINT("[ ]", "%s", "================================================================================")
|
||||||
#define DEBUG_INFO(fmt, ...) DEBUG_PRINT("[I]", fmt, ##__VA_ARGS__)
|
#define DEBUG_INFO(fmt, ...) DEBUG_PRINT("[I]", fmt, ##__VA_ARGS__)
|
||||||
|
|||||||
@@ -22,8 +22,12 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
|||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdatomic.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
|
struct stFrameBuffer
|
||||||
{
|
{
|
||||||
@@ -35,31 +39,63 @@ const size_t FrameBufferStructSize = sizeof(FrameBuffer);
|
|||||||
|
|
||||||
void framebuffer_wait(const FrameBuffer * frame, size_t size)
|
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 * restrict dst,
|
||||||
bool framebuffer_read(const FrameBuffer * frame, void * dst, size_t dstpitch,
|
size_t dstpitch, size_t height, size_t width, size_t bpp, size_t pitch)
|
||||||
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;
|
uint_least32_t rp = 0;
|
||||||
size_t y = 0;
|
size_t y = 0;
|
||||||
const size_t linewidth = width * bpp;
|
const size_t linewidth = width * bpp;
|
||||||
|
const size_t blocks = linewidth / 64;
|
||||||
|
const size_t left = linewidth % 64;
|
||||||
|
|
||||||
while(y < height)
|
while(y < height)
|
||||||
{
|
{
|
||||||
uint_least32_t wp;
|
uint_least32_t wp;
|
||||||
|
int spinCount = 0;
|
||||||
|
|
||||||
/* spinlock */
|
/* spinlock */
|
||||||
do
|
wp = atomic_load_explicit(&frame->wp, memory_order_acquire);
|
||||||
wp = atomic_load_explicit(&frame->wp, memory_order_relaxed);
|
while(wp - rp < linewidth)
|
||||||
while(wp - rp < pitch);
|
{
|
||||||
|
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;
|
rp += pitch;
|
||||||
d += dstpitch;
|
d += dstpitch - linewidth;
|
||||||
++y;
|
++y;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,11 +112,18 @@ bool framebuffer_read_fn(const FrameBuffer * frame, size_t height, size_t width,
|
|||||||
while(y < height)
|
while(y < height)
|
||||||
{
|
{
|
||||||
uint_least32_t wp;
|
uint_least32_t wp;
|
||||||
|
int spinCount = 0;
|
||||||
|
|
||||||
/* spinlock */
|
/* spinlock */
|
||||||
do
|
wp = atomic_load_explicit(&frame->wp, memory_order_acquire);
|
||||||
wp = atomic_load_explicit(&frame->wp, memory_order_relaxed);
|
while(wp - rp < linewidth)
|
||||||
while(wp - rp < pitch);
|
{
|
||||||
|
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))
|
if (!fn(opaque, frame->data + rp, linewidth))
|
||||||
return false;
|
return false;
|
||||||
@@ -97,18 +140,47 @@ bool framebuffer_read_fn(const FrameBuffer * frame, size_t height, size_t width,
|
|||||||
*/
|
*/
|
||||||
void framebuffer_prepare(FrameBuffer * frame)
|
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 */
|
/* copy in chunks */
|
||||||
while(size)
|
while(size > 63)
|
||||||
{
|
{
|
||||||
size_t copy = size < FB_CHUNK_SIZE ? FB_CHUNK_SIZE : size;
|
__m128i *_d = (__m128i *)d;
|
||||||
memcpy(frame->data + frame->wp, src, copy);
|
__m128i *_s = (__m128i *)s;
|
||||||
atomic_fetch_add(&frame->wp, copy);
|
__m128i v1 = _mm_stream_load_si128(_s + 0);
|
||||||
size -= copy;
|
__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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ add_library(lg_common_platform_code STATIC
|
|||||||
thread.c
|
thread.c
|
||||||
event.c
|
event.c
|
||||||
ivshmem.c
|
ivshmem.c
|
||||||
|
time.c
|
||||||
)
|
)
|
||||||
|
|
||||||
if(ENABLE_BACKTRACE)
|
if(ENABLE_BACKTRACE)
|
||||||
@@ -20,4 +21,5 @@ endif()
|
|||||||
target_link_libraries(lg_common_platform_code
|
target_link_libraries(lg_common_platform_code
|
||||||
lg_common
|
lg_common
|
||||||
pthread
|
pthread
|
||||||
|
rt
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define _GNU_SOURCE
|
|
||||||
#include "common/crash.h"
|
#include "common/crash.h"
|
||||||
#include "common/debug.h"
|
#include "common/debug.h"
|
||||||
|
|
||||||
|
|||||||
@@ -83,19 +83,20 @@ bool lgWaitEventAbs(LGEvent * handle, struct timespec * ts)
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool ret = true;
|
bool ret = true;
|
||||||
|
int res;
|
||||||
while(ret && !atomic_load(&handle->flag))
|
while(ret && !atomic_load(&handle->flag))
|
||||||
{
|
{
|
||||||
if (!ts)
|
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;
|
ret = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
switch(pthread_cond_timedwait(&handle->cond, &handle->mutex, ts))
|
switch((res = pthread_cond_timedwait(&handle->cond, &handle->mutex, ts)))
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
break;
|
break;
|
||||||
@@ -106,7 +107,7 @@ bool lgWaitEventAbs(LGEvent * handle, struct timespec * ts)
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
ret = false;
|
ret = false;
|
||||||
DEBUG_ERROR("Timed wait failed");
|
DEBUG_ERROR("Timed wait failed (err: %d)", res);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,6 +146,9 @@ bool lgWaitEventNS(LGEvent * handle, unsigned int timeout)
|
|||||||
|
|
||||||
bool lgWaitEvent(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);
|
return lgWaitEventNS(handle, timeout * 1000000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ bool lgCreateThread(const char * name, LGThreadFunction function, void * opaque,
|
|||||||
*handle = NULL;
|
*handle = NULL;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pthread_setname_np((*handle)->handle, name);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
109
common/src/platform/linux/time.c
Normal file
109
common/src/platform/linux/time.c
Normal 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);
|
||||||
|
}
|
||||||
@@ -37,7 +37,7 @@ void DebugWinError(const char * file, const unsigned int line, const char * func
|
|||||||
if (buffer[i] == '\n' || buffer[i] == '\r')
|
if (buffer[i] == '\n' || buffer[i] == '\r')
|
||||||
buffer[i] = 0;
|
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);
|
LocalFree(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
0
c-host/.gitignore → host/.gitignore
vendored
0
c-host/.gitignore → host/.gitignore
vendored
@@ -20,8 +20,8 @@ Currently only Windows is supported however there is some initial support for Li
|
|||||||
6. configure the project and build it
|
6. configure the project and build it
|
||||||
|
|
||||||
```
|
```
|
||||||
mkdir LookingGlass/c-host/build
|
mkdir LookingGlass/host/build
|
||||||
cd LookingGlass/c-host/build
|
cd LookingGlass/host/build
|
||||||
cmake -G "MSYS Makefiles" ..
|
cmake -G "MSYS Makefiles" ..
|
||||||
make
|
make
|
||||||
```
|
```
|
||||||
@@ -44,17 +44,46 @@ cmake -DCMAKE_TOOLCHAIN_FILE=../toolchain-mingw64.cmake ..
|
|||||||
make
|
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?
|
## Where is the log?
|
||||||
|
|
||||||
It is in your user's temp directory:
|
It is in your user's temp directory:
|
||||||
|
|
||||||
%TEMP%\looking-glass-host.txt
|
%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
|
## Why does this version require Administrator privileges
|
||||||
|
|
||||||
@@ -67,6 +67,7 @@ typedef struct CapturePointer
|
|||||||
|
|
||||||
bool shapeUpdate;
|
bool shapeUpdate;
|
||||||
CaptureFormat format;
|
CaptureFormat format;
|
||||||
|
unsigned int hx, hy;
|
||||||
unsigned int width, height;
|
unsigned int width, height;
|
||||||
unsigned int pitch;
|
unsigned int pitch;
|
||||||
}
|
}
|
||||||
@@ -27,3 +27,4 @@ void app_quit();
|
|||||||
|
|
||||||
// these must be implemented for each OS
|
// these must be implemented for each OS
|
||||||
const char * os_getExecutable();
|
const char * os_getExecutable();
|
||||||
|
const char * os_getDataPath();
|
||||||
@@ -30,6 +30,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
|||||||
struct app
|
struct app
|
||||||
{
|
{
|
||||||
const char * executable;
|
const char * executable;
|
||||||
|
char * dataPath;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct app app = { 0 };
|
struct app app = { 0 };
|
||||||
@@ -37,7 +38,13 @@ struct app app = { 0 };
|
|||||||
int main(int argc, char * argv[])
|
int main(int argc, char * argv[])
|
||||||
{
|
{
|
||||||
app.executable = argv[0];
|
app.executable = argv[0];
|
||||||
|
|
||||||
|
struct passwd * pw = getpwuid(getuid());
|
||||||
|
alloc_sprintf(&app.dataPath, "%s/", pw->pw_dir);
|
||||||
|
|
||||||
int result = app_main(argc, argv);
|
int result = app_main(argc, argv);
|
||||||
|
|
||||||
|
free(app.dataPath);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,3 +64,8 @@ const char * os_getExecutable()
|
|||||||
{
|
{
|
||||||
return app.executable;
|
return app.executable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char * os_getDataPath()
|
||||||
|
{
|
||||||
|
return app.dataPath;
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ include_directories(
|
|||||||
|
|
||||||
add_library(platform_Windows STATIC
|
add_library(platform_Windows STATIC
|
||||||
src/platform.c
|
src/platform.c
|
||||||
|
src/service.c
|
||||||
src/mousehook.c
|
src/mousehook.c
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -23,9 +24,19 @@ target_link_libraries(platform_Windows
|
|||||||
"${PROJECT_BINARY_DIR}/resource.o"
|
"${PROJECT_BINARY_DIR}/resource.o"
|
||||||
lg_common
|
lg_common
|
||||||
capture
|
capture
|
||||||
|
|
||||||
|
userenv
|
||||||
|
wtsapi32
|
||||||
|
psapi
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(platform_Windows
|
target_include_directories(platform_Windows
|
||||||
PRIVATE
|
PRIVATE
|
||||||
src
|
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)
|
||||||
@@ -26,12 +26,26 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
|||||||
#include "common/event.h"
|
#include "common/event.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <stdatomic.h>
|
||||||
|
#include <unistd.h>
|
||||||
#include <dxgi.h>
|
#include <dxgi.h>
|
||||||
#include <d3d11.h>
|
#include <d3d11.h>
|
||||||
#include <d3dcommon.h>
|
#include <d3dcommon.h>
|
||||||
|
|
||||||
#include "dxgi_extra.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)
|
#define LOCKED(x) INTERLOCKED_SECTION(this->deviceContextLock, x)
|
||||||
|
|
||||||
enum TextureState
|
enum TextureState
|
||||||
@@ -43,7 +57,7 @@ enum TextureState
|
|||||||
|
|
||||||
typedef struct Texture
|
typedef struct Texture
|
||||||
{
|
{
|
||||||
enum TextureState state;
|
volatile enum TextureState state;
|
||||||
ID3D11Texture2D * tex;
|
ID3D11Texture2D * tex;
|
||||||
D3D11_MAPPED_SUBRESOURCE map;
|
D3D11_MAPPED_SUBRESOURCE map;
|
||||||
}
|
}
|
||||||
@@ -70,7 +84,7 @@ struct iface
|
|||||||
Texture * texture;
|
Texture * texture;
|
||||||
int texRIndex;
|
int texRIndex;
|
||||||
int texWIndex;
|
int texWIndex;
|
||||||
volatile int texReady;
|
atomic_int texReady;
|
||||||
bool needsRelease;
|
bool needsRelease;
|
||||||
|
|
||||||
CaptureGetPointerBuffer getPointerBufferFn;
|
CaptureGetPointerBuffer getPointerBufferFn;
|
||||||
@@ -130,9 +144,9 @@ static void dxgi_initOptions()
|
|||||||
{
|
{
|
||||||
.module = "dxgi",
|
.module = "dxgi",
|
||||||
.name = "useAcquireLock",
|
.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,
|
.type = OPTION_TYPE_BOOL,
|
||||||
.value.x_bool = false
|
.value.x_bool = true
|
||||||
},
|
},
|
||||||
{0}
|
{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("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("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("To fix this, install and run the Looking Glass host as a service.");
|
||||||
DEBUG_INFO("https://docs.microsoft.com/en-us/sysinternals/downloads/psexec");
|
DEBUG_INFO("looking-glass-host.exe InstallService");
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is required for DXGI 1.5 support to function
|
// this is required for DXGI 1.5 support to function
|
||||||
@@ -216,7 +230,7 @@ static bool dxgi_init()
|
|||||||
this->stop = false;
|
this->stop = false;
|
||||||
this->texRIndex = 0;
|
this->texRIndex = 0;
|
||||||
this->texWIndex = 0;
|
this->texWIndex = 0;
|
||||||
this->texReady = 0;
|
atomic_store(&this->texReady, 0);
|
||||||
|
|
||||||
lgResetEvent(this->frameEvent);
|
lgResetEvent(this->frameEvent);
|
||||||
|
|
||||||
@@ -385,6 +399,25 @@ static bool dxgi_init()
|
|||||||
|
|
||||||
// bump up our priority
|
// 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;
|
IDXGIDevice * dxgi;
|
||||||
status = ID3D11Device_QueryInterface(this->device, &IID_IDXGIDevice, (void **)&dxgi);
|
status = ID3D11Device_QueryInterface(this->device, &IID_IDXGIDevice, (void **)&dxgi);
|
||||||
if (FAILED(status))
|
if (FAILED(status))
|
||||||
@@ -674,6 +707,15 @@ static CaptureResult dxgi_capture()
|
|||||||
DXGI_OUTDUPL_FRAME_INFO frameInfo;
|
DXGI_OUTDUPL_FRAME_INFO frameInfo;
|
||||||
IDXGIResource * res;
|
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
|
// release the prior frame
|
||||||
result = dxgi_releaseFrame();
|
result = dxgi_releaseFrame();
|
||||||
if (result != CAPTURE_RESULT_OK)
|
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
|
// check if the texture is free, if not skip the frame to keep up
|
||||||
if (tex->state == TEXTURE_STATE_UNUSED)
|
if (tex->state == TEXTURE_STATE_UNUSED)
|
||||||
{
|
{
|
||||||
ID3D11Texture2D * src;
|
copyFrame = true;
|
||||||
status = IDXGIResource_QueryInterface(res, &IID_ID3D11Texture2D, (void **)&src);
|
status = IDXGIResource_QueryInterface(res, &IID_ID3D11Texture2D, (void **)&src);
|
||||||
if (FAILED(status))
|
if (FAILED(status))
|
||||||
{
|
{
|
||||||
@@ -713,18 +755,50 @@ static CaptureResult dxgi_capture()
|
|||||||
IDXGIResource_Release(res);
|
IDXGIResource_Release(res);
|
||||||
return CAPTURE_RESULT_ERROR;
|
return CAPTURE_RESULT_ERROR;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LOCKED({
|
IDXGIResource_Release(res);
|
||||||
// issue the copy from GPU to CPU RAM and release the src
|
|
||||||
|
// 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,
|
ID3D11DeviceContext_CopyResource(this->deviceContext,
|
||||||
(ID3D11Resource *)tex->tex, (ID3D11Resource *)src);
|
(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);
|
ID3D11Texture2D_Release(src);
|
||||||
|
|
||||||
// set the state, and signal
|
// set the state, and signal
|
||||||
tex->state = TEXTURE_STATE_PENDING_MAP;
|
tex->state = TEXTURE_STATE_PENDING_MAP;
|
||||||
INTERLOCKED_INC(&this->texReady);
|
if (atomic_fetch_add_explicit(&this->texReady, 1, memory_order_relaxed) == 0)
|
||||||
lgSignalEvent(this->frameEvent);
|
lgSignalEvent(this->frameEvent);
|
||||||
|
|
||||||
// advance the write index
|
// advance the write index
|
||||||
@@ -734,15 +808,62 @@ static CaptureResult dxgi_capture()
|
|||||||
// update the last frame time
|
// update the last frame time
|
||||||
this->frameTime.QuadPart = frameInfo.LastPresentTime.QuadPart;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
IDXGIResource_Release(res);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
// if the pointer has moved or changed state
|
CURSORINFO ci = { .cbSize = sizeof(CURSORINFO) };
|
||||||
bool postPointer = false;
|
if (!GetCursorInfo(&ci))
|
||||||
CapturePointer pointer = { 0 };
|
{
|
||||||
void * pointerShape = NULL;
|
DEBUG_WINERROR("GetCursorInfo failed", GetLastError());
|
||||||
UINT pointerShapeSize = 0;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (frameInfo.LastMouseUpdateTime.QuadPart)
|
if (frameInfo.LastMouseUpdateTime.QuadPart)
|
||||||
{
|
{
|
||||||
@@ -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
|
// post back the pointer information
|
||||||
if (postPointer)
|
if (postPointer)
|
||||||
{
|
{
|
||||||
@@ -821,29 +905,41 @@ static CaptureResult dxgi_waitFrame(CaptureFrame * frame)
|
|||||||
assert(this->initialized);
|
assert(this->initialized);
|
||||||
|
|
||||||
// NOTE: the event may be signaled when there are no frames available
|
// 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))
|
if (!lgWaitEvent(this->frameEvent, 1000))
|
||||||
return CAPTURE_RESULT_TIMEOUT;
|
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;
|
return CAPTURE_RESULT_TIMEOUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
Texture * tex = &this->texture[this->texRIndex];
|
Texture * tex = &this->texture[this->texRIndex];
|
||||||
|
|
||||||
// try to map the resource, but don't wait for it
|
// try to map the resource, but don't wait for it
|
||||||
|
for (int i = 0; ; ++i)
|
||||||
|
{
|
||||||
HRESULT status;
|
HRESULT status;
|
||||||
LOCKED({status = ID3D11DeviceContext_Map(this->deviceContext, (ID3D11Resource*)tex->tex, 0, D3D11_MAP_READ, 0x100000L, &tex->map);});
|
LOCKED({status = ID3D11DeviceContext_Map(this->deviceContext, (ID3D11Resource*)tex->tex, 0, D3D11_MAP_READ, 0x100000L, &tex->map);});
|
||||||
if (status == DXGI_ERROR_WAS_STILL_DRAWING)
|
if (status == DXGI_ERROR_WAS_STILL_DRAWING)
|
||||||
|
{
|
||||||
|
if (i == 100)
|
||||||
return CAPTURE_RESULT_TIMEOUT;
|
return CAPTURE_RESULT_TIMEOUT;
|
||||||
|
|
||||||
|
usleep(1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (FAILED(status))
|
if (FAILED(status))
|
||||||
{
|
{
|
||||||
DEBUG_WINERROR("Failed to map the texture", status);
|
DEBUG_WINERROR("Failed to map the texture", status);
|
||||||
return CAPTURE_RESULT_ERROR;
|
return CAPTURE_RESULT_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
tex->state = TEXTURE_STATE_MAPPED;
|
tex->state = TEXTURE_STATE_MAPPED;
|
||||||
|
|
||||||
frame->width = this->width;
|
frame->width = this->width;
|
||||||
@@ -852,7 +948,7 @@ static CaptureResult dxgi_waitFrame(CaptureFrame * frame)
|
|||||||
frame->stride = this->stride;
|
frame->stride = this->stride;
|
||||||
frame->format = this->format;
|
frame->format = this->format;
|
||||||
|
|
||||||
INTERLOCKED_DEC(&this->texReady);
|
atomic_fetch_sub_explicit(&this->texReady, 1, memory_order_release);
|
||||||
return CAPTURE_RESULT_OK;
|
return CAPTURE_RESULT_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,8 +352,8 @@ static int pointerThread(void * unused)
|
|||||||
}
|
}
|
||||||
|
|
||||||
this->mouseVisible = pointer.visible;
|
this->mouseVisible = pointer.visible;
|
||||||
this->mouseHotX = pointer.x;
|
this->mouseHotX = pointer.hx;
|
||||||
this->mouseHotY = pointer.y;
|
this->mouseHotY = pointer.hy;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (events[0])
|
if (events[0])
|
||||||
@@ -288,8 +288,8 @@ CaptureResult NvFBCToSysGetCursor(NvFBCHandle handle, CapturePointer * pointer,
|
|||||||
return CAPTURE_RESULT_ERROR;
|
return CAPTURE_RESULT_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
pointer->x = params.dwXHotSpot;
|
pointer->hx = params.dwXHotSpot;
|
||||||
pointer->y = params.dwYHotSpot;
|
pointer->hy = params.dwYHotSpot;
|
||||||
pointer->width = params.dwWidth;
|
pointer->width = params.dwWidth;
|
||||||
pointer->height = params.dwHeight;
|
pointer->height = params.dwHeight;
|
||||||
pointer->pitch = params.dwPitch;
|
pointer->pitch = params.dwPitch;
|
||||||
198
host/platform/Windows/installer.nsi
Normal file
198
host/platform/Windows/installer.nsi
Normal 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
|
||||||
@@ -18,6 +18,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
|
#include "service.h"
|
||||||
#include "windows/mousehook.h"
|
#include "windows/mousehook.h"
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
@@ -31,7 +32,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
|||||||
#include "common/locking.h"
|
#include "common/locking.h"
|
||||||
#include "common/thread.h"
|
#include "common/thread.h"
|
||||||
|
|
||||||
#define ID_MENU_OPEN_LOG 3000
|
#define ID_MENU_SHOW_LOG 3000
|
||||||
#define ID_MENU_EXIT 3001
|
#define ID_MENU_EXIT 3001
|
||||||
|
|
||||||
struct AppState
|
struct AppState
|
||||||
@@ -44,6 +45,8 @@ struct AppState
|
|||||||
|
|
||||||
char executable[MAX_PATH + 1];
|
char executable[MAX_PATH + 1];
|
||||||
HWND messageWnd;
|
HWND messageWnd;
|
||||||
|
NOTIFYICONDATA iconData;
|
||||||
|
UINT trayRestartMsg;
|
||||||
HMENU trayMenu;
|
HMENU trayMenu;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -54,6 +57,30 @@ HWND MessageHWND;
|
|||||||
typedef NTSTATUS (__stdcall *ZwSetTimerResolution_t)(ULONG RequestedResolution, BOOLEAN Set, PULONG ActualResolution);
|
typedef NTSTATUS (__stdcall *ZwSetTimerResolution_t)(ULONG RequestedResolution, BOOLEAN Set, PULONG ActualResolution);
|
||||||
static ZwSetTimerResolution_t ZwSetTimerResolution = NULL;
|
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)
|
LRESULT CALLBACK DummyWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
||||||
{
|
{
|
||||||
switch(msg)
|
switch(msg)
|
||||||
@@ -86,41 +113,39 @@ LRESULT CALLBACK DummyWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (clicked == ID_MENU_EXIT ) app_quit();
|
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");
|
const char * logFile = option_get_string("os", "logFile");
|
||||||
if (strcmp(logFile, "stderr") == 0)
|
if (strcmp(logFile, "stderr") == 0)
|
||||||
DEBUG_INFO("Ignoring request to open the logFile, logging to stderr");
|
DEBUG_INFO("Ignoring request to open the logFile, logging to stderr");
|
||||||
else
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
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)
|
static int appThread(void * opaque)
|
||||||
{
|
{
|
||||||
// register our TrayIcon
|
RegisterTrayIcon();
|
||||||
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);
|
|
||||||
|
|
||||||
int result = app_main(app.argc, app.argv);
|
int result = app_main(app.argc, app.argv);
|
||||||
|
|
||||||
Shell_NotifyIcon(NIM_DELETE, &iconData);
|
Shell_NotifyIcon(NIM_DELETE, &app.iconData);
|
||||||
mouseHook_remove();
|
mouseHook_remove();
|
||||||
SendMessage(app.messageWnd, WM_DESTROY, 0, 0);
|
SendMessage(app.messageWnd, WM_DESTROY, 0, 0);
|
||||||
return result;
|
return result;
|
||||||
@@ -144,6 +169,21 @@ static BOOL WINAPI CtrlHandler(DWORD dwCtrlType)
|
|||||||
|
|
||||||
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
|
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 */
|
/* 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))
|
if (!IsDebuggerPresent() && AttachConsole(ATTACH_PARENT_PROCESS))
|
||||||
{
|
{
|
||||||
@@ -183,19 +223,6 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
|||||||
option_register(options);
|
option_register(options);
|
||||||
free(logFilePath);
|
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
|
// setup a handler for ctrl+c
|
||||||
SetConsoleCtrlHandler(CtrlHandler, TRUE);
|
SetConsoleCtrlHandler(CtrlHandler, TRUE);
|
||||||
|
|
||||||
@@ -209,19 +236,29 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
|||||||
wx.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
|
wx.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
|
||||||
wx.hCursor = LoadCursor(NULL, IDC_ARROW);
|
wx.hCursor = LoadCursor(NULL, IDC_ARROW);
|
||||||
wx.hbrBackground = (HBRUSH)COLOR_APPWORKSPACE;
|
wx.hbrBackground = (HBRUSH)COLOR_APPWORKSPACE;
|
||||||
if (!RegisterClassEx(&wx))
|
ATOM class;
|
||||||
|
if (!(class = RegisterClassEx(&wx)))
|
||||||
{
|
{
|
||||||
DEBUG_ERROR("Failed to register message window class");
|
DEBUG_ERROR("Failed to register message window class");
|
||||||
result = -1;
|
result = -1;
|
||||||
goto finish;
|
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
|
// set the global
|
||||||
MessageHWND = app.messageWnd;
|
MessageHWND = app.messageWnd;
|
||||||
|
|
||||||
app.trayMenu = CreatePopupMenu();
|
app.trayMenu = CreatePopupMenu();
|
||||||
AppendMenu(app.trayMenu, MF_STRING , ID_MENU_OPEN_LOG, "Open Log File");
|
AppendMenu(app.trayMenu, MF_STRING , ID_MENU_SHOW_LOG, "Log File Location");
|
||||||
AppendMenu(app.trayMenu, MF_SEPARATOR, 0 , NULL );
|
AppendMenu(app.trayMenu, MF_SEPARATOR, 0 , NULL );
|
||||||
AppendMenu(app.trayMenu, MF_STRING , ID_MENU_EXIT , "Exit" );
|
AppendMenu(app.trayMenu, MF_STRING , ID_MENU_EXIT , "Exit" );
|
||||||
|
|
||||||
@@ -303,6 +340,24 @@ const char * os_getExecutable()
|
|||||||
return app.executable;
|
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()
|
HWND os_getMessageWnd()
|
||||||
{
|
{
|
||||||
return app.messageWnd;
|
return app.messageWnd;
|
||||||
688
host/platform/Windows/src/service.c
Normal file
688
host/platform/Windows/src/service.c
Normal 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;
|
||||||
|
}
|
||||||
3
host/platform/Windows/src/service.h
Normal file
3
host/platform/Windows/src/service.h
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
bool HandleService(int argc, char * argv[]);
|
||||||
@@ -38,6 +38,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#define CONFIG_FILE "looking-glass-host.ini"
|
||||||
|
|
||||||
#define ALIGN_DN(x) ((uintptr_t)(x) & ~0x7F)
|
#define ALIGN_DN(x) ((uintptr_t)(x) & ~0x7F)
|
||||||
#define ALIGN_UP(x) ALIGN_DN(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))
|
#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
|
struct app
|
||||||
{
|
{
|
||||||
PLGMPHost lgmp;
|
PLGMPHost lgmp;
|
||||||
|
|
||||||
PLGMPHostQueue pointerQueue;
|
PLGMPHostQueue pointerQueue;
|
||||||
PLGMPMemory pointerMemory[LGMP_Q_POINTER_LEN];
|
PLGMPMemory pointerMemory[LGMP_Q_POINTER_LEN];
|
||||||
|
LG_Lock pointerLock;
|
||||||
|
CapturePointer pointerInfo;
|
||||||
PLGMPMemory pointerShape;
|
PLGMPMemory pointerShape;
|
||||||
bool pointerShapeValid;
|
bool pointerShapeValid;
|
||||||
unsigned int pointerIndex;
|
unsigned int pointerIndex;
|
||||||
@@ -77,8 +89,7 @@ struct app
|
|||||||
|
|
||||||
CaptureInterface * iface;
|
CaptureInterface * iface;
|
||||||
|
|
||||||
bool running;
|
enum AppState state;
|
||||||
bool reinit;
|
|
||||||
LGTimer * lgmpTimer;
|
LGTimer * lgmpTimer;
|
||||||
LGThread * frameThread;
|
LGThread * frameThread;
|
||||||
};
|
};
|
||||||
@@ -91,7 +102,7 @@ static bool lgmpTimer(void * opaque)
|
|||||||
if ((status = lgmpHostProcess(app.lgmp)) != LGMP_OK)
|
if ((status = lgmpHostProcess(app.lgmp)) != LGMP_OK)
|
||||||
{
|
{
|
||||||
DEBUG_ERROR("lgmpHostProcess Failed: %s", lgmpStatusString(status));
|
DEBUG_ERROR("lgmpHostProcess Failed: %s", lgmpStatusString(status));
|
||||||
app.running = false;
|
app.state = APP_STATE_SHUTDOWN;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +118,7 @@ static int frameThread(void * opaque)
|
|||||||
CaptureFrame frame = { 0 };
|
CaptureFrame frame = { 0 };
|
||||||
const long pageSize = sysinfo_getPageSize();
|
const long pageSize = sysinfo_getPageSize();
|
||||||
|
|
||||||
while(app.running)
|
while(app.state == APP_STATE_RUNNING)
|
||||||
{
|
{
|
||||||
//wait until there is room in the queue
|
//wait until there is room in the queue
|
||||||
if(lgmpHostQueuePending(app.frameQueue) == LGMP_Q_FRAME_LEN)
|
if(lgmpHostQueuePending(app.frameQueue) == LGMP_Q_FRAME_LEN)
|
||||||
@@ -124,7 +135,7 @@ static int frameThread(void * opaque)
|
|||||||
|
|
||||||
case CAPTURE_RESULT_REINIT:
|
case CAPTURE_RESULT_REINIT:
|
||||||
{
|
{
|
||||||
app.reinit = true;
|
app.state = APP_STATE_RESTART;
|
||||||
DEBUG_INFO("Frame thread reinit");
|
DEBUG_INFO("Frame thread reinit");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -201,13 +212,7 @@ static int frameThread(void * opaque)
|
|||||||
|
|
||||||
bool startThreads()
|
bool startThreads()
|
||||||
{
|
{
|
||||||
app.running = true;
|
app.state = APP_STATE_RUNNING;
|
||||||
if (!lgCreateTimer(100, lgmpTimer, NULL, &app.lgmpTimer))
|
|
||||||
{
|
|
||||||
DEBUG_ERROR("Failed to create the LGMP timer");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!lgCreateThread("FrameThread", frameThread, NULL, &app.frameThread))
|
if (!lgCreateThread("FrameThread", frameThread, NULL, &app.frameThread))
|
||||||
{
|
{
|
||||||
DEBUG_ERROR("Failed to create the frame thread");
|
DEBUG_ERROR("Failed to create the frame thread");
|
||||||
@@ -221,8 +226,9 @@ bool stopThreads()
|
|||||||
{
|
{
|
||||||
bool ok = true;
|
bool ok = true;
|
||||||
|
|
||||||
app.running = false;
|
|
||||||
app.iface->stop();
|
app.iface->stop();
|
||||||
|
if (app.state != APP_STATE_SHUTDOWN)
|
||||||
|
app.state = APP_STATE_IDLE;
|
||||||
|
|
||||||
if (app.frameThread && !lgJoinThread(app.frameThread, NULL))
|
if (app.frameThread && !lgJoinThread(app.frameThread, NULL))
|
||||||
{
|
{
|
||||||
@@ -231,18 +237,19 @@ bool stopThreads()
|
|||||||
}
|
}
|
||||||
app.frameThread = NULL;
|
app.frameThread = NULL;
|
||||||
|
|
||||||
if (app.lgmpTimer)
|
|
||||||
{
|
|
||||||
lgTimerDestroy(app.lgmpTimer);
|
|
||||||
app.lgmpTimer = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool captureStart()
|
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();
|
const unsigned int maxFrameSize = app.iface->getMaxFrameSize();
|
||||||
if (maxFrameSize > app.maxFrameSize)
|
if (maxFrameSize > app.maxFrameSize)
|
||||||
@@ -256,31 +263,33 @@ static bool captureStart()
|
|||||||
return startThreads();
|
return startThreads();
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool captureRestart()
|
static bool captureStop()
|
||||||
{
|
{
|
||||||
DEBUG_INFO("==== [ Capture Restart ] ====");
|
DEBUG_INFO("==== [ Capture Stop ] ====");
|
||||||
if (!stopThreads())
|
if (!stopThreads())
|
||||||
return false;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!captureStart())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool captureRestart()
|
||||||
|
{
|
||||||
|
return captureStop() && captureStart();
|
||||||
|
}
|
||||||
|
|
||||||
bool captureGetPointerBuffer(void ** data, uint32_t * size)
|
bool captureGetPointerBuffer(void ** data, uint32_t * size)
|
||||||
{
|
{
|
||||||
// spin until there is room
|
// spin until there is room
|
||||||
while(lgmpHostQueuePending(app.pointerQueue) == LGMP_Q_POINTER_LEN)
|
while(lgmpHostQueuePending(app.pointerQueue) == LGMP_Q_POINTER_LEN)
|
||||||
{
|
{
|
||||||
DEBUG_INFO("pending");
|
usleep(1);
|
||||||
if (!app.running)
|
if (app.state == APP_STATE_RUNNING)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,14 +299,12 @@ bool captureGetPointerBuffer(void ** data, uint32_t * size)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void capturePostPointerBuffer(CapturePointer pointer)
|
static void sendPointer(bool newClient)
|
||||||
{
|
{
|
||||||
PLGMPMemory mem;
|
PLGMPMemory mem;
|
||||||
const bool newClient = lgmpHostQueueNewSubs(app.pointerQueue) > 0;
|
if (app.pointerInfo.shapeUpdate || newClient)
|
||||||
|
|
||||||
if (pointer.shapeUpdate || newClient)
|
|
||||||
{
|
{
|
||||||
if (pointer.shapeUpdate)
|
if (!newClient)
|
||||||
{
|
{
|
||||||
// swap the latest shape buffer out of rotation
|
// swap the latest shape buffer out of rotation
|
||||||
PLGMPMemory tmp = app.pointerShape;
|
PLGMPMemory tmp = app.pointerShape;
|
||||||
@@ -309,32 +316,32 @@ void capturePostPointerBuffer(CapturePointer pointer)
|
|||||||
mem = app.pointerShape;
|
mem = app.pointerShape;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
|
||||||
mem = app.pointerMemory[app.pointerIndex];
|
mem = app.pointerMemory[app.pointerIndex];
|
||||||
|
|
||||||
if (++app.pointerIndex == LGMP_Q_POINTER_LEN)
|
if (++app.pointerIndex == LGMP_Q_POINTER_LEN)
|
||||||
app.pointerIndex = 0;
|
app.pointerIndex = 0;
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t flags = 0;
|
uint32_t flags = 0;
|
||||||
KVMFRCursor *cursor = lgmpHostMemPtr(mem);
|
KVMFRCursor *cursor = lgmpHostMemPtr(mem);
|
||||||
|
|
||||||
if (pointer.positionUpdate)
|
if (app.pointerInfo.positionUpdate || newClient)
|
||||||
{
|
{
|
||||||
flags |= CURSOR_FLAG_POSITION;
|
flags |= CURSOR_FLAG_POSITION;
|
||||||
cursor->x = pointer.x;
|
cursor->x = app.pointerInfo.x;
|
||||||
cursor->y = pointer.y;
|
cursor->y = app.pointerInfo.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pointer.visible)
|
if (app.pointerInfo.visible)
|
||||||
flags |= CURSOR_FLAG_VISIBLE;
|
flags |= CURSOR_FLAG_VISIBLE;
|
||||||
|
|
||||||
if (pointer.shapeUpdate)
|
if (app.pointerInfo.shapeUpdate)
|
||||||
{
|
{
|
||||||
// remember which slot has the latest shape
|
cursor->hx = app.pointerInfo.hx;
|
||||||
cursor->width = pointer.width;
|
cursor->hy = app.pointerInfo.hy;
|
||||||
cursor->height = pointer.height;
|
cursor->width = app.pointerInfo.width;
|
||||||
cursor->pitch = pointer.pitch;
|
cursor->height = app.pointerInfo.height;
|
||||||
switch(pointer.format)
|
cursor->pitch = app.pointerInfo.pitch;
|
||||||
|
switch(app.pointerInfo.format)
|
||||||
{
|
{
|
||||||
case CAPTURE_FMT_COLOR : cursor->type = CURSOR_TYPE_COLOR ; break;
|
case CAPTURE_FMT_COLOR : cursor->type = CURSOR_TYPE_COLOR ; break;
|
||||||
case CAPTURE_FMT_MONO : cursor->type = CURSOR_TYPE_MONOCHROME ; break;
|
case CAPTURE_FMT_MONO : cursor->type = CURSOR_TYPE_MONOCHROME ; break;
|
||||||
@@ -348,7 +355,7 @@ void capturePostPointerBuffer(CapturePointer pointer)
|
|||||||
app.pointerShapeValid = true;
|
app.pointerShapeValid = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((pointer.shapeUpdate || newClient) && app.pointerShapeValid)
|
if ((app.pointerInfo.shapeUpdate || newClient) && app.pointerShapeValid)
|
||||||
flags |= CURSOR_FLAG_SHAPE;
|
flags |= CURSOR_FLAG_SHAPE;
|
||||||
|
|
||||||
LGMP_STATUS status;
|
LGMP_STATUS status;
|
||||||
@@ -361,10 +368,31 @@ void capturePostPointerBuffer(CapturePointer pointer)
|
|||||||
}
|
}
|
||||||
|
|
||||||
DEBUG_ERROR("lgmpHostQueuePost Failed (Pointer): %s", lgmpStatusString(status));
|
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
|
// this is called from the platform specific startup routine
|
||||||
int app_main(int argc, char * argv[])
|
int app_main(int argc, char * argv[])
|
||||||
{
|
{
|
||||||
@@ -379,7 +407,22 @@ int app_main(int argc, char * argv[])
|
|||||||
CaptureInterfaces[i]->initOptions();
|
CaptureInterfaces[i]->initOptions();
|
||||||
|
|
||||||
// try load values from a config file
|
// 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
|
// parse the command line arguments
|
||||||
if (!option_parse(argc, argv))
|
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("Max Pointer Size : %u KiB", (unsigned int)MAX_POINTER_SIZE / 1024);
|
||||||
DEBUG_INFO("KVMFR Version : %u", KVMFR_VERSION);
|
DEBUG_INFO("KVMFR Version : %u", KVMFR_VERSION);
|
||||||
|
|
||||||
KVMFR udata = {
|
const KVMFR udata = {
|
||||||
.magic = KVMFR_MAGIC,
|
.magic = KVMFR_MAGIC,
|
||||||
.version = KVMFR_VERSION
|
.version = KVMFR_VERSION,
|
||||||
|
.hostver = BUILD_VERSION
|
||||||
};
|
};
|
||||||
|
|
||||||
LGMP_STATUS status;
|
LGMP_STATUS status;
|
||||||
@@ -446,6 +490,7 @@ int app_main(int argc, char * argv[])
|
|||||||
DEBUG_ERROR("lgmpHostMemAlloc Failed (Pointer): %s", lgmpStatusString(status));
|
DEBUG_ERROR("lgmpHostMemAlloc Failed (Pointer): %s", lgmpStatusString(status));
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
memset(lgmpHostMemPtr(app.pointerMemory[i]), 0, MAX_POINTER_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
app.pointerShapeValid = false;
|
app.pointerShapeValid = false;
|
||||||
@@ -496,22 +541,56 @@ int app_main(int argc, char * argv[])
|
|||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DEBUG_INFO("Using : %s", iface->getName());
|
||||||
|
|
||||||
|
app.state = APP_STATE_RUNNING;
|
||||||
app.iface = iface;
|
app.iface = iface;
|
||||||
|
|
||||||
|
LG_LOCK_INIT(app.pointerLock);
|
||||||
|
|
||||||
|
if (!lgCreateTimer(100, lgmpTimer, NULL, &app.lgmpTimer))
|
||||||
|
{
|
||||||
|
DEBUG_ERROR("Failed to create the LGMP timer");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
while(app.state != APP_STATE_SHUTDOWN)
|
||||||
|
{
|
||||||
|
if(lgmpHostQueueHasSubs(app.pointerQueue) ||
|
||||||
|
lgmpHostQueueHasSubs(app.frameQueue))
|
||||||
|
{
|
||||||
if (!captureStart())
|
if (!captureStart())
|
||||||
{
|
{
|
||||||
exitcode = -1;
|
exitcode = -1;
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
while(app.running)
|
else
|
||||||
{
|
{
|
||||||
if (app.reinit && !captureRestart())
|
usleep(100000);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
while(app.state != APP_STATE_SHUTDOWN && (
|
||||||
|
lgmpHostQueueHasSubs(app.pointerQueue) ||
|
||||||
|
lgmpHostQueueHasSubs(app.frameQueue)))
|
||||||
|
{
|
||||||
|
if (app.state == APP_STATE_RESTART)
|
||||||
|
{
|
||||||
|
if (!captureRestart())
|
||||||
{
|
{
|
||||||
exitcode = -1;
|
exitcode = -1;
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
app.reinit = false;
|
app.state = APP_STATE_RUNNING;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lgmpHostQueueNewSubs(app.pointerQueue) > 0)
|
||||||
|
{
|
||||||
|
LG_LOCK(app.pointerLock);
|
||||||
|
sendPointer(true);
|
||||||
|
LG_UNLOCK(app.pointerLock);
|
||||||
|
}
|
||||||
|
|
||||||
switch(iface->capture())
|
switch(iface->capture())
|
||||||
{
|
{
|
||||||
@@ -522,12 +601,7 @@ int app_main(int argc, char * argv[])
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
case CAPTURE_RESULT_REINIT:
|
case CAPTURE_RESULT_REINIT:
|
||||||
if (!captureRestart())
|
app.state = APP_STATE_RESTART;
|
||||||
{
|
|
||||||
exitcode = -1;
|
|
||||||
goto exit;
|
|
||||||
}
|
|
||||||
app.reinit = false;
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
case CAPTURE_RESULT_ERROR:
|
case CAPTURE_RESULT_ERROR:
|
||||||
@@ -537,9 +611,17 @@ int app_main(int argc, char * argv[])
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (app.state != APP_STATE_SHUTDOWN)
|
||||||
|
DEBUG_INFO("No subscribers, going to sleep...");
|
||||||
|
captureStop();
|
||||||
|
}
|
||||||
|
|
||||||
finish:
|
finish:
|
||||||
stopThreads();
|
stopThreads();
|
||||||
|
|
||||||
exit:
|
exit:
|
||||||
|
lgTimerDestroy(app.lgmpTimer);
|
||||||
|
LG_LOCK_FREE(app.pointerLock);
|
||||||
|
|
||||||
iface->deinit();
|
iface->deinit();
|
||||||
iface->free();
|
iface->free();
|
||||||
@@ -558,5 +640,5 @@ fail:
|
|||||||
|
|
||||||
void app_quit()
|
void app_quit()
|
||||||
{
|
{
|
||||||
app.running = false;
|
app.state = APP_STATE_SHUTDOWN;
|
||||||
}
|
}
|
||||||
13
obs/lg.c
13
obs/lg.c
@@ -1,3 +1,5 @@
|
|||||||
|
#define _GNU_SOURCE //needed for pthread_setname_np
|
||||||
|
|
||||||
#include <obs/obs-module.h>
|
#include <obs/obs-module.h>
|
||||||
#include <obs/util/threading.h>
|
#include <obs/util/threading.h>
|
||||||
|
|
||||||
@@ -193,6 +195,7 @@ static void lgUpdate(void * data, obs_data_t * settings)
|
|||||||
|
|
||||||
this->state = STATE_STARTING;
|
this->state = STATE_STARTING;
|
||||||
pthread_create(&this->frameThread, NULL, frameThread, this);
|
pthread_create(&this->frameThread, NULL, frameThread, this);
|
||||||
|
pthread_setname_np(this->frameThread, "LGFrameThread");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void lgVideoTick(void * data, float seconds)
|
static void lgVideoTick(void * data, float seconds)
|
||||||
@@ -212,6 +215,16 @@ static void lgVideoTick(void * data, float seconds)
|
|||||||
return;
|
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 = lgmpClientProcess(this->frameQueue, &msg)) != LGMP_OK)
|
||||||
{
|
{
|
||||||
if (status == LGMP_ERR_QUEUE_EMPTY)
|
if (status == LGMP_ERR_QUEUE_EMPTY)
|
||||||
|
|||||||
Submodule repos/LGMP updated: 19efde39f6...2a1477550c
Reference in New Issue
Block a user