mirror of
https://github.com/gnif/LookingGlass.git
synced 2025-11-17 23:42:20 +00:00
Compare commits
82 Commits
| 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:
|
||||
Host GPU:
|
||||
Guest GPU:
|
||||
Host Kernel version:
|
||||
Host QEMU version:
|
||||
If you are looking for help or support please use one of the following methods
|
||||
|
||||
Please describe what were you doing when the problem occured. If the Windows host application crashed please check for file named `looking-glass-host.dmp` and attach it to this bug report.
|
||||
Create a New Topic on the Level1Tech's forum under the Looking Glass category:
|
||||
* https://forum.level1techs.com/c/software/lookingGlass/142
|
||||
|
||||
**Reports that do not include this information will be ignored and closed**
|
||||
Ask for help in #looking-glass in the VFIO discord server
|
||||
* https://discord.gg/4ahCn4c
|
||||
|
||||
*Issues that are not bug reports or feature requests will be closed & ignored*
|
||||
|
||||
### Errors that are not bugs
|
||||
|
||||
Some errors generated by the LG client are not bugs, but rather issues with your
|
||||
system's configuration and/or timing. Please do not report these, but rather use
|
||||
one of the above resources to ask for advice/help.
|
||||
|
||||
* `LGMP_ERR_QUEUE_UNSUBSCRIBED` - Failure to heed advice on things such as
|
||||
using `isolcpus` and CPU pinning may result in this message, especially if you
|
||||
are over-taxing your CPU.
|
||||
|
||||
* `Could not create an SDL window: *` - Failure to create a SDL window is not an
|
||||
issue with Looking Glass but rather a more substantial issue with your system,
|
||||
such as missing hardware support for the RGBA32 pixmap format, or missing
|
||||
required OpenGL EGL features.
|
||||
|
||||
* `The host application is not compatible with this client` - The Looking Glass
|
||||
Host application in Windows is the incorrect version and is not compatible,
|
||||
you need to make sure you run matching versions of both the host and client
|
||||
applications.
|
||||
|
||||
### Bug Report Required Information
|
||||
|
||||
The entire (not truncated) output from the client application (if applicable).
|
||||
To obtain this run `looking-glass-client` in a terminal.
|
||||
|
||||
```
|
||||
PASTE CLIENT OUTPUT HERE
|
||||
```
|
||||
|
||||
The entire (not truncated) log file from the host application (if applicable).
|
||||
To obtain this locate the log file on your system, it will be in one of the
|
||||
following two locations depending on how you are launching the Looking Glass Host
|
||||
application:
|
||||
|
||||
* C:\Windows\Temp\looking-glass.txt
|
||||
* C:\Users\YOUR_USER\AppData\Local\Temp\looking-glass.txt
|
||||
|
||||
This log may be quite long, please delete the file first and then proceed to
|
||||
launch the host and reproduce the issue so that the log only contains the
|
||||
pertinent information.
|
||||
|
||||
|
||||
```
|
||||
PASTE HOST LOG FILE CONTENTS HERE
|
||||
```
|
||||
|
||||
If the client is unexpetedly exiting without a backtrace, please provide one via
|
||||
gdb with the command `thread apply all bt`. If you are unsure how to do this
|
||||
please watch the video below on how to perform a Debug build and generate this
|
||||
backtrace.
|
||||
|
||||
https://www.youtube.com/watch?v=EqxxJK9Yo64
|
||||
|
||||
|
||||
```
|
||||
PASTE FULL BACKTRACE HERE
|
||||
```
|
||||
|
||||
15
README.md
15
README.md
@@ -4,6 +4,7 @@ An extremely low latency KVMFR (KVM FrameRelay) implementation for guests with
|
||||
VGA PCI Passthrough.
|
||||
|
||||
* Project Website: https://looking-glass.hostfission.com
|
||||
* Getting Started: https://looking-glass.hostfission.com/wiki/Installation
|
||||
|
||||
## Donations
|
||||
|
||||
@@ -23,18 +24,20 @@ support me directly using the following platforms.
|
||||
|
||||
** IMPORTANT **
|
||||
This project contains submodules that must be checked out if building from the
|
||||
git repository!
|
||||
git repository! If you are not a developer and just want to compile Looking
|
||||
Glass please download the source archive from the website instead:
|
||||
|
||||
https://looking-glass.hostfission.com/downloads
|
||||
|
||||
Please also be sure to see the following files for more information
|
||||
Note: The `README.md` files are slowly being deprecated from this project in
|
||||
favor of the wiki at https://looking-glass.hostfission.com/wiki, and as such the
|
||||
information in these files may be dated.
|
||||
|
||||
* [client/README.md](client/README.md)
|
||||
* [c-host/README.md](c-host/README.md)
|
||||
* [host/README.md](host/README.md)
|
||||
* [module/README.md](module/README.md)
|
||||
|
||||
## Obtaining and using Looking Glass
|
||||
|
||||
Please see https://looking-glass.hostfission.com/wiki/
|
||||
|
||||
## Latest Version
|
||||
|
||||
If you would like to use the latest bleeding edge version of Looking Glass please
|
||||
|
||||
@@ -142,6 +142,7 @@ Command line arguments will override any options loaded from the config files.
|
||||
| spice:clipboardToVM | | yes | Allow the clipboard to be syncronized TO the VM |
|
||||
| spice:clipboardToLocal | | yes | Allow the clipboard to be syncronized FROM the VM |
|
||||
| spice:scaleCursor | -j | yes | Scale cursor input position to screen size when up/down scaled |
|
||||
| spice:captureOnStart | | no | Capture mouse and keyboard on start |
|
||||
|------------------------------------------------------------------------------------------------------------------|
|
||||
|
||||
|--------------------------------------------------------------------------|
|
||||
|
||||
@@ -33,6 +33,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
(x)->create && \
|
||||
(x)->initialize && \
|
||||
(x)->deinitialize && \
|
||||
(x)->on_restart && \
|
||||
(x)->on_resize && \
|
||||
(x)->on_mouse_shape && \
|
||||
(x)->on_mouse_event && \
|
||||
@@ -87,6 +88,7 @@ typedef void (* LG_RendererSetup)();
|
||||
typedef bool (* LG_RendererCreate )(void ** opaque, const LG_RendererParams params);
|
||||
typedef bool (* LG_RendererInitialize )(void * opaque, Uint32 * sdlFlags);
|
||||
typedef void (* LG_RendererDeInitialize)(void * opaque);
|
||||
typedef void (* LG_RendererOnRestart )(void * opaque);
|
||||
typedef void (* LG_RendererOnResize )(void * opaque, const int width, const int height, const LG_RendererRect destRect);
|
||||
typedef bool (* LG_RendererOnMouseShape)(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data);
|
||||
typedef bool (* LG_RendererOnMouseEvent)(void * opaque, const bool visible , const int x, const int y);
|
||||
@@ -103,6 +105,7 @@ typedef struct LG_Renderer
|
||||
LG_RendererCreate create;
|
||||
LG_RendererInitialize initialize;
|
||||
LG_RendererDeInitialize deinitialize;
|
||||
LG_RendererOnRestart on_restart;
|
||||
LG_RendererOnResize on_resize;
|
||||
LG_RendererOnMouseShape on_mouse_shape;
|
||||
LG_RendererOnMouseEvent on_mouse_event;
|
||||
|
||||
@@ -68,6 +68,7 @@ struct Inst
|
||||
EGL_Alert * alert; // the alert display
|
||||
|
||||
LG_RendererFormat format;
|
||||
bool start;
|
||||
uint64_t waitFadeTime;
|
||||
bool waitDone;
|
||||
|
||||
@@ -225,6 +226,15 @@ void egl_deinitialize(void * opaque)
|
||||
free(this);
|
||||
}
|
||||
|
||||
void egl_on_restart(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
eglDestroyContext(this->display, this->frameContext);
|
||||
this->frameContext = NULL;
|
||||
this->start = false;
|
||||
}
|
||||
|
||||
void egl_on_resize(void * opaque, const int width, const int height, const LG_RendererRect destRect)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
@@ -340,6 +350,7 @@ bool egl_on_frame_event(void * opaque, const LG_RendererFormat format, const Fra
|
||||
return false;
|
||||
}
|
||||
|
||||
this->start = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -532,7 +543,10 @@ bool egl_render(void * opaque, SDL_Window * window)
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
if (egl_desktop_render(this->desktop, this->translateX, this->translateY, this->scaleX, this->scaleY, this->useNearest))
|
||||
if (this->start && egl_desktop_render(this->desktop,
|
||||
this->translateX, this->translateY,
|
||||
this->scaleX , this->scaleY ,
|
||||
this->useNearest))
|
||||
{
|
||||
if (!this->waitFadeTime)
|
||||
this->waitFadeTime = microtime() + SPLASH_FADE_TIME;
|
||||
@@ -559,6 +573,11 @@ bool egl_render(void * opaque, SDL_Window * window)
|
||||
if (!this->waitDone)
|
||||
egl_splash_render(this->splash, a, this->splashRatio);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!this->start)
|
||||
egl_splash_render(this->splash, 1.0f, this->splashRatio);
|
||||
}
|
||||
|
||||
if (this->showAlert)
|
||||
{
|
||||
@@ -595,6 +614,7 @@ struct LG_Renderer LGR_EGL =
|
||||
.create = egl_create,
|
||||
.initialize = egl_initialize,
|
||||
.deinitialize = egl_deinitialize,
|
||||
.on_restart = egl_on_restart,
|
||||
.on_resize = egl_on_resize,
|
||||
.on_mouse_shape = egl_on_mouse_shape,
|
||||
.on_mouse_event = egl_on_mouse_event,
|
||||
|
||||
@@ -44,6 +44,7 @@ struct EGL_FPS
|
||||
EGL_Model * model;
|
||||
|
||||
bool ready;
|
||||
int iwidth, iheight;
|
||||
float width, height;
|
||||
|
||||
// uniforms
|
||||
@@ -144,14 +145,22 @@ void egl_fps_update(EGL_FPS * fps, const float avgFPS, const float renderFPS)
|
||||
return;
|
||||
}
|
||||
|
||||
egl_texture_setup(
|
||||
fps->texture,
|
||||
EGL_PF_BGRA,
|
||||
bmp->width ,
|
||||
bmp->height,
|
||||
bmp->width * bmp->bpp,
|
||||
false
|
||||
);
|
||||
if (fps->iwidth != bmp->width || fps->iheight != bmp->height)
|
||||
{
|
||||
fps->iwidth = bmp->width;
|
||||
fps->iheight = bmp->height;
|
||||
fps->width = (float)bmp->width;
|
||||
fps->height = (float)bmp->height;
|
||||
|
||||
egl_texture_setup(
|
||||
fps->texture,
|
||||
EGL_PF_BGRA,
|
||||
bmp->width ,
|
||||
bmp->height,
|
||||
bmp->width * bmp->bpp,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
egl_texture_update
|
||||
(
|
||||
@@ -159,10 +168,7 @@ void egl_fps_update(EGL_FPS * fps, const float avgFPS, const float renderFPS)
|
||||
bmp->pixels
|
||||
);
|
||||
|
||||
fps->width = bmp->width;
|
||||
fps->height = bmp->height;
|
||||
fps->ready = true;
|
||||
|
||||
fps->font->release(fps->fontObj, bmp);
|
||||
}
|
||||
|
||||
@@ -187,4 +193,4 @@ void egl_fps_render(EGL_FPS * fps, const float scaleX, const float scaleY)
|
||||
egl_model_render(fps->model);
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
#include <SDL2/SDL_egl.h>
|
||||
|
||||
#define TEXTURE_COUNT 3
|
||||
/* this must be a multiple of 2 */
|
||||
#define TEXTURE_COUNT 2
|
||||
|
||||
struct Tex
|
||||
{
|
||||
@@ -41,19 +42,9 @@ struct Tex
|
||||
GLsync sync;
|
||||
};
|
||||
|
||||
union TexState
|
||||
struct TexState
|
||||
{
|
||||
_Atomic(uint32_t) v;
|
||||
struct
|
||||
{
|
||||
/*
|
||||
* w = write
|
||||
* u = upload
|
||||
* s = schedule
|
||||
* d = display
|
||||
*/
|
||||
_Atomic(int8_t) w, u, s, d;
|
||||
};
|
||||
_Atomic(uint8_t) w, u, s, d;
|
||||
};
|
||||
|
||||
struct EGL_Texture
|
||||
@@ -73,8 +64,9 @@ struct EGL_Texture
|
||||
GLenum dataType;
|
||||
size_t pboBufferSize;
|
||||
|
||||
union TexState state;
|
||||
struct Tex tex[TEXTURE_COUNT];
|
||||
struct TexState state;
|
||||
int textureCount;
|
||||
struct Tex tex[TEXTURE_COUNT];
|
||||
};
|
||||
|
||||
bool egl_texture_init(EGL_Texture ** texture)
|
||||
@@ -98,7 +90,7 @@ void egl_texture_free(EGL_Texture ** texture)
|
||||
if ((*texture)->planeCount > 0)
|
||||
glDeleteSamplers((*texture)->planeCount, (*texture)->samplers);
|
||||
|
||||
for(int i = 0; i < ((*texture)->streaming ? TEXTURE_COUNT : 1); ++i)
|
||||
for(int i = 0; i < (*texture)->textureCount; ++i)
|
||||
{
|
||||
struct Tex * t = &(*texture)->tex[i];
|
||||
if (t->hasPBO)
|
||||
@@ -123,56 +115,68 @@ void egl_texture_free(EGL_Texture ** texture)
|
||||
*texture = NULL;
|
||||
}
|
||||
|
||||
static bool egl_texture_map(EGL_Texture * texture)
|
||||
static bool egl_texture_map(EGL_Texture * texture, uint8_t i)
|
||||
{
|
||||
// release old PBOs and delete and re-create the buffers
|
||||
for(int i = 0; i < TEXTURE_COUNT; ++i)
|
||||
{
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[i].pbo);
|
||||
texture->tex[i].map = glMapBufferRange(
|
||||
GL_PIXEL_UNPACK_BUFFER,
|
||||
0,
|
||||
texture->pboBufferSize,
|
||||
GL_MAP_WRITE_BIT |
|
||||
GL_MAP_UNSYNCHRONIZED_BIT |
|
||||
GL_MAP_INVALIDATE_BUFFER_BIT
|
||||
);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[i].pbo);
|
||||
texture->tex[i].map = glMapBufferRange(
|
||||
GL_PIXEL_UNPACK_BUFFER,
|
||||
0,
|
||||
texture->pboBufferSize,
|
||||
GL_MAP_WRITE_BIT |
|
||||
GL_MAP_UNSYNCHRONIZED_BIT |
|
||||
GL_MAP_INVALIDATE_BUFFER_BIT
|
||||
);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
if (!texture->tex[i].map)
|
||||
{
|
||||
EGL_ERROR("glMapBufferRange failed for %d of %lu bytes", i, texture->pboBufferSize);
|
||||
return false;
|
||||
}
|
||||
if (!texture->tex[i].map)
|
||||
{
|
||||
EGL_ERROR("glMapBufferRange failed for %d of %lu bytes", i, texture->pboBufferSize);
|
||||
return false;
|
||||
}
|
||||
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void egl_texture_unmap(EGL_Texture * texture)
|
||||
static void egl_texture_unmap(EGL_Texture * texture, uint8_t i)
|
||||
{
|
||||
for(int i = 0; i < TEXTURE_COUNT; ++i)
|
||||
{
|
||||
if (!texture->tex[i].map)
|
||||
continue;
|
||||
if (!texture->tex[i].map)
|
||||
return;
|
||||
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[i].pbo);
|
||||
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
|
||||
texture->tex[i].map = NULL;
|
||||
}
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[i].pbo);
|
||||
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
texture->tex[i].map = NULL;
|
||||
}
|
||||
|
||||
bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_t width, size_t height, size_t stride, bool streaming)
|
||||
{
|
||||
int planeCount;
|
||||
|
||||
texture->pixFmt = pixFmt;
|
||||
texture->width = width;
|
||||
texture->height = height;
|
||||
texture->stride = stride;
|
||||
texture->streaming = streaming;
|
||||
texture->ready = false;
|
||||
atomic_store_explicit(&texture->state.v, 0, memory_order_relaxed);
|
||||
if (texture->streaming)
|
||||
{
|
||||
for(int i = 0; i < texture->textureCount; ++i)
|
||||
{
|
||||
egl_texture_unmap(texture, i);
|
||||
if (texture->tex[i].hasPBO)
|
||||
{
|
||||
glDeleteBuffers(1, &texture->tex[i].pbo);
|
||||
texture->tex[i].hasPBO = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
texture->pixFmt = pixFmt;
|
||||
texture->width = width;
|
||||
texture->height = height;
|
||||
texture->stride = stride;
|
||||
texture->streaming = streaming;
|
||||
texture->textureCount = streaming ? TEXTURE_COUNT : 1;
|
||||
texture->ready = false;
|
||||
|
||||
atomic_store_explicit(&texture->state.w, 0, memory_order_relaxed);
|
||||
atomic_store_explicit(&texture->state.u, 0, memory_order_relaxed);
|
||||
atomic_store_explicit(&texture->state.s, 0, memory_order_relaxed);
|
||||
atomic_store_explicit(&texture->state.d, 0, memory_order_relaxed);
|
||||
|
||||
switch(pixFmt)
|
||||
{
|
||||
@@ -245,7 +249,7 @@ bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_
|
||||
if (texture->planeCount > 0)
|
||||
glDeleteSamplers(texture->planeCount, texture->samplers);
|
||||
|
||||
for(int i = 0; i < TEXTURE_COUNT; ++i)
|
||||
for(int i = 0; i < texture->textureCount; ++i)
|
||||
{
|
||||
if (texture->planeCount > 0)
|
||||
glDeleteTextures(texture->planeCount, texture->tex[i].t);
|
||||
@@ -264,7 +268,7 @@ bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_
|
||||
texture->planeCount = planeCount;
|
||||
}
|
||||
|
||||
for(int i = 0; i < (streaming ? TEXTURE_COUNT : 1); ++i)
|
||||
for(int i = 0; i < texture->textureCount; ++i)
|
||||
{
|
||||
for(int p = 0; p < planeCount; ++p)
|
||||
{
|
||||
@@ -275,14 +279,11 @@ bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
egl_texture_unmap(texture);
|
||||
if (!streaming)
|
||||
return true;
|
||||
|
||||
// release old PBOs and delete and re-create the buffers
|
||||
for(int i = 0; i < TEXTURE_COUNT; ++i)
|
||||
for(int i = 0; i < texture->textureCount; ++i)
|
||||
{
|
||||
if (texture->tex[i].hasPBO)
|
||||
glDeleteBuffers(1, &texture->tex[i].pbo);
|
||||
|
||||
glGenBuffers(1, &texture->tex[i].pbo);
|
||||
texture->tex[i].hasPBO = true;
|
||||
|
||||
@@ -295,9 +296,6 @@ bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_
|
||||
);
|
||||
}
|
||||
|
||||
if (!egl_texture_map(texture))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -318,23 +316,25 @@ bool egl_texture_update(EGL_Texture * texture, const uint8_t * buffer)
|
||||
{
|
||||
if (texture->streaming)
|
||||
{
|
||||
union TexState s;
|
||||
s.v = atomic_load_explicit(&texture->state.v, memory_order_acquire);
|
||||
const uint8_t sw =
|
||||
atomic_load_explicit(&texture->state.w, memory_order_acquire);
|
||||
|
||||
const uint8_t next = (s.w + 1) % TEXTURE_COUNT;
|
||||
if (next == s.u)
|
||||
if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == (uint8_t)(sw + 1))
|
||||
{
|
||||
egl_warn_slow();
|
||||
return true;
|
||||
}
|
||||
|
||||
memcpy(texture->tex[s.w].map, buffer, texture->pboBufferSize);
|
||||
atomic_store_explicit(&texture->state.w, next, memory_order_release);
|
||||
const uint8_t t = sw % TEXTURE_COUNT;
|
||||
if (!egl_texture_map(texture, t))
|
||||
return EGL_TEX_STATUS_ERROR;
|
||||
|
||||
memcpy(texture->tex[t].map, buffer, texture->pboBufferSize);
|
||||
atomic_fetch_add_explicit(&texture->state.w, 1, memory_order_release);
|
||||
egl_texture_unmap(texture, t);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Non streaming, this is NOT thread safe */
|
||||
|
||||
for(int p = 0; p < texture->planeCount; ++p)
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, texture->tex[0].t[p]);
|
||||
@@ -352,19 +352,22 @@ bool egl_texture_update_from_frame(EGL_Texture * texture, const FrameBuffer * fr
|
||||
if (!texture->streaming)
|
||||
return false;
|
||||
|
||||
union TexState s;
|
||||
s.v = atomic_load_explicit(&texture->state.v, memory_order_acquire);
|
||||
const uint8_t sw =
|
||||
atomic_load_explicit(&texture->state.w, memory_order_acquire);
|
||||
|
||||
const uint8_t next = (s.w + 1) % TEXTURE_COUNT;
|
||||
if (next == s.u)
|
||||
if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == (uint8_t)(sw + 1))
|
||||
{
|
||||
egl_warn_slow();
|
||||
return true;
|
||||
}
|
||||
|
||||
const uint8_t t = sw % TEXTURE_COUNT;
|
||||
if (!egl_texture_map(texture, t))
|
||||
return EGL_TEX_STATUS_ERROR;
|
||||
|
||||
framebuffer_read(
|
||||
frame,
|
||||
texture->tex[s.w].map,
|
||||
texture->tex[t].map,
|
||||
texture->stride,
|
||||
texture->height,
|
||||
texture->width,
|
||||
@@ -372,7 +375,9 @@ bool egl_texture_update_from_frame(EGL_Texture * texture, const FrameBuffer * fr
|
||||
texture->stride
|
||||
);
|
||||
|
||||
atomic_store_explicit(&texture->state.w, next, memory_order_release);
|
||||
atomic_fetch_add_explicit(&texture->state.w, 1, memory_order_release);
|
||||
egl_texture_unmap(texture, t);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -381,62 +386,63 @@ enum EGL_TexStatus egl_texture_process(EGL_Texture * texture)
|
||||
if (!texture->streaming)
|
||||
return EGL_TEX_STATUS_OK;
|
||||
|
||||
union TexState s;
|
||||
s.v = atomic_load_explicit(&texture->state.v, memory_order_acquire);
|
||||
const uint8_t su =
|
||||
atomic_load_explicit(&texture->state.u, memory_order_acquire);
|
||||
|
||||
const uint8_t nextu = (s.u + 1) % TEXTURE_COUNT;
|
||||
if (s.u == s.w || nextu == s.s || nextu == s.d)
|
||||
const uint8_t nextu = su + 1;
|
||||
if (
|
||||
su == atomic_load_explicit(&texture->state.w, memory_order_acquire) ||
|
||||
nextu == atomic_load_explicit(&texture->state.s, memory_order_acquire) ||
|
||||
nextu == atomic_load_explicit(&texture->state.d, memory_order_acquire))
|
||||
return texture->ready ? EGL_TEX_STATUS_OK : EGL_TEX_STATUS_NOTREADY;
|
||||
|
||||
/* update the texture */
|
||||
egl_texture_unmap(texture);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[s.u].pbo);
|
||||
const uint8_t t = su % TEXTURE_COUNT;
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[t].pbo);
|
||||
for(int p = 0; p < texture->planeCount; ++p)
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, texture->tex[s.u].t[p]);
|
||||
glBindTexture(GL_TEXTURE_2D, texture->tex[t].t[p]);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, texture->planes[p][2]);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture->planes[p][0], texture->planes[p][1],
|
||||
texture->format, texture->dataType, (const void *)texture->offsets[p]);
|
||||
|
||||
}
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
/* create a fence to prevent usage before the update is complete */
|
||||
texture->tex[s.u].sync =
|
||||
glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
texture->tex[t].sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
|
||||
/* we must flush to ensure the sync is in the command buffer */
|
||||
glFlush();
|
||||
|
||||
atomic_store_explicit(&texture->state.u, nextu, memory_order_release);
|
||||
|
||||
/* remap the for the next update */
|
||||
egl_texture_map(texture);
|
||||
|
||||
texture->ready = true;
|
||||
atomic_fetch_add_explicit(&texture->state.u, 1, memory_order_release);
|
||||
|
||||
return EGL_TEX_STATUS_OK;
|
||||
}
|
||||
|
||||
enum EGL_TexStatus egl_texture_bind(EGL_Texture * texture)
|
||||
{
|
||||
union TexState s;
|
||||
s.v = atomic_load_explicit(&texture->state.v, memory_order_acquire);
|
||||
uint8_t ss = atomic_load_explicit(&texture->state.s, memory_order_acquire);
|
||||
uint8_t sd = atomic_load_explicit(&texture->state.d, memory_order_acquire);
|
||||
|
||||
if (texture->streaming)
|
||||
{
|
||||
if (!texture->ready)
|
||||
return EGL_TEX_STATUS_NOTREADY;
|
||||
|
||||
if (texture->tex[s.s].sync != 0)
|
||||
const uint8_t t = ss % TEXTURE_COUNT;
|
||||
if (texture->tex[t].sync != 0)
|
||||
{
|
||||
switch(glClientWaitSync(texture->tex[s.s].sync, 0, 20000000)) // 20ms
|
||||
switch(glClientWaitSync(texture->tex[t].sync, 0, 20000000)) // 20ms
|
||||
{
|
||||
case GL_ALREADY_SIGNALED:
|
||||
case GL_CONDITION_SATISFIED:
|
||||
glDeleteSync(texture->tex[s.s].sync);
|
||||
texture->tex[s.s].sync = 0;
|
||||
glDeleteSync(texture->tex[t].sync);
|
||||
texture->tex[t].sync = 0;
|
||||
|
||||
s.s = (s.s + 1) % TEXTURE_COUNT;
|
||||
atomic_store_explicit(&texture->state.s, s.s, memory_order_release);
|
||||
ss = atomic_fetch_add_explicit(&texture->state.s, 1,
|
||||
memory_order_release) + 1;
|
||||
break;
|
||||
|
||||
case GL_TIMEOUT_EXPIRED:
|
||||
@@ -444,25 +450,23 @@ enum EGL_TexStatus egl_texture_bind(EGL_Texture * texture)
|
||||
|
||||
case GL_WAIT_FAILED:
|
||||
case GL_INVALID_VALUE:
|
||||
glDeleteSync(texture->tex[s.s].sync);
|
||||
texture->tex[s.s].sync = 0;
|
||||
glDeleteSync(texture->tex[t].sync);
|
||||
texture->tex[t].sync = 0;
|
||||
EGL_ERROR("glClientWaitSync failed");
|
||||
return EGL_TEX_STATUS_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
const int8_t nextd = (s.d + 1) % TEXTURE_COUNT;
|
||||
if (s.d != s.s && nextd != s.s)
|
||||
{
|
||||
s.d = nextd;
|
||||
atomic_store_explicit(&texture->state.d, nextd, memory_order_release);
|
||||
}
|
||||
if (ss != sd && ss != (uint8_t)(sd + 1))
|
||||
sd = atomic_fetch_add_explicit(&texture->state.d, 1,
|
||||
memory_order_release) + 1;
|
||||
}
|
||||
|
||||
const uint8_t t = sd % TEXTURE_COUNT;
|
||||
for(int i = 0; i < texture->planeCount; ++i)
|
||||
{
|
||||
glActiveTexture(GL_TEXTURE0 + i);
|
||||
glBindTexture(GL_TEXTURE_2D, texture->tex[s.d].t[i]);
|
||||
glBindTexture(GL_TEXTURE_2D, texture->tex[t].t[i]);
|
||||
glBindSampler(i, texture->samplers[i]);
|
||||
}
|
||||
|
||||
|
||||
@@ -295,6 +295,12 @@ void opengl_deinitialize(void * opaque)
|
||||
free(this);
|
||||
}
|
||||
|
||||
void opengl_on_restart(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
this->waiting = true;
|
||||
}
|
||||
|
||||
void opengl_on_resize(void * opaque, const int width, const int height, const LG_RendererRect destRect)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
@@ -823,6 +829,7 @@ const LG_Renderer LGR_OpenGL =
|
||||
.create = opengl_create,
|
||||
.initialize = opengl_initialize,
|
||||
.deinitialize = opengl_deinitialize,
|
||||
.on_restart = opengl_on_restart,
|
||||
.on_resize = opengl_on_resize,
|
||||
.on_mouse_shape = opengl_on_mouse_shape,
|
||||
.on_mouse_event = opengl_on_mouse_event,
|
||||
|
||||
@@ -247,6 +247,13 @@ static struct Option options[] =
|
||||
.type = OPTION_TYPE_INT,
|
||||
.value.x_int = 0,
|
||||
},
|
||||
{
|
||||
.module = "input",
|
||||
.name = "mouseRedraw",
|
||||
.description = "Mouse movements trigger redraws (ignores FPS minimum)",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true,
|
||||
},
|
||||
|
||||
// spice options
|
||||
{
|
||||
@@ -309,6 +316,13 @@ static struct Option options[] =
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true
|
||||
},
|
||||
{
|
||||
.module = "spice",
|
||||
.name = "captureOnStart",
|
||||
.description = "Capture mouse and keyboard on start",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false
|
||||
},
|
||||
{0}
|
||||
};
|
||||
|
||||
@@ -392,6 +406,7 @@ bool config_load(int argc, char * argv[])
|
||||
params.escapeKey = option_get_int ("input", "escapeKey" );
|
||||
params.hideMouse = option_get_bool ("input", "hideCursor" );
|
||||
params.mouseSens = option_get_int ("input", "mouseSens" );
|
||||
params.mouseRedraw = option_get_bool ("input", "mouseRedraw" );
|
||||
|
||||
params.minimizeOnFocusLoss = option_get_bool("win", "minimizeOnFocusLoss");
|
||||
|
||||
@@ -413,6 +428,7 @@ bool config_load(int argc, char * argv[])
|
||||
}
|
||||
|
||||
params.scaleMouseInput = option_get_bool("spice", "scaleCursor");
|
||||
params.captureOnStart = option_get_bool("spice", "captureOnStart");
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -34,6 +34,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <assert.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
#if SDL_VIDEO_DRIVER_X11_XINPUT2
|
||||
// because SDL2 sucks and we need to turn it off
|
||||
@@ -69,6 +70,8 @@ static LGThread *t_cursor = NULL;
|
||||
static LGThread *t_frame = NULL;
|
||||
static SDL_Cursor *cursor = NULL;
|
||||
|
||||
static atomic_uint a_framesPending = 0;
|
||||
|
||||
struct AppState state;
|
||||
|
||||
// this structure is initialized in config.c
|
||||
@@ -138,7 +141,7 @@ static int renderThread(void * unused)
|
||||
{
|
||||
if (!state.lgr->render_startup(state.lgrData, state.window))
|
||||
{
|
||||
state.running = false;
|
||||
state.state = APP_STATE_SHUTDOWN;
|
||||
|
||||
/* unblock threads waiting on the condition */
|
||||
lgSignalEvent(e_startup);
|
||||
@@ -152,26 +155,16 @@ static int renderThread(void * unused)
|
||||
struct timespec time;
|
||||
clock_gettime(CLOCK_REALTIME, &time);
|
||||
|
||||
while(state.running)
|
||||
while(state.state != APP_STATE_SHUTDOWN)
|
||||
{
|
||||
if (state.frameTime > 0)
|
||||
{
|
||||
tsAdd(&time, state.frameTime);
|
||||
|
||||
// if our clock is too far out of sync, resync it
|
||||
// this can happen when switching to/from a TTY, or due to clock drift
|
||||
// we only check this once every 100 frames
|
||||
if (++resyncCheck == 100)
|
||||
{
|
||||
resyncCheck = 0;
|
||||
|
||||
struct timespec end, diff;
|
||||
clock_gettime(CLOCK_REALTIME, &end);
|
||||
tsDiff(&diff, &time, &end);
|
||||
if (diff.tv_sec > 0 || diff.tv_nsec > 1000000000 || // 100ms
|
||||
diff.tv_sec < 0 || diff.tv_nsec < 0) // underflow
|
||||
clock_gettime(CLOCK_REALTIME, &time);
|
||||
clock_gettime(CLOCK_REALTIME, &time);
|
||||
}
|
||||
tsAdd(&time, state.frameTime);
|
||||
}
|
||||
|
||||
if (state.lgrResize)
|
||||
@@ -193,12 +186,12 @@ static int renderThread(void * unused)
|
||||
|
||||
if (state.renderTime > 1e9)
|
||||
{
|
||||
const float avgUPS = 1000.0f / (((float)state.renderTime / state.frameCount ) / 1e6f);
|
||||
const float avgUPS = 1000.0f / (((float)state.renderTime / atomic_load_explicit(&state.frameCount, memory_order_acquire)) / 1e6f);
|
||||
const float avgFPS = 1000.0f / (((float)state.renderTime / state.renderCount) / 1e6f);
|
||||
state.lgr->update_fps(state.lgrData, avgUPS, avgFPS);
|
||||
|
||||
atomic_store_explicit(&state.frameCount, 0, memory_order_release);
|
||||
state.renderTime = 0;
|
||||
state.frameCount = 0;
|
||||
state.renderCount = 0;
|
||||
}
|
||||
}
|
||||
@@ -214,10 +207,29 @@ static int renderThread(void * unused)
|
||||
}
|
||||
|
||||
if (state.frameTime > 0)
|
||||
lgWaitEventAbs(e_frame, &time);
|
||||
{
|
||||
/* if there are frames pending already, don't wait on the event */
|
||||
if (atomic_load_explicit(&a_framesPending, memory_order_acquire) > 0)
|
||||
if (atomic_fetch_sub_explicit(&a_framesPending, 1, memory_order_release) > 1)
|
||||
continue;
|
||||
|
||||
if (lgWaitEventAbs(e_frame, &time) && state.frameTime > 0)
|
||||
{
|
||||
/* only resync the timer if we got an early frame */
|
||||
struct timespec now, diff;
|
||||
clock_gettime(CLOCK_REALTIME, &now);
|
||||
tsDiff(&diff, &now, &time);
|
||||
if (diff.tv_sec == 0 && diff.tv_nsec < state.frameTime)
|
||||
{
|
||||
resyncCheck = 0;
|
||||
memcpy(&time, &now, sizeof(struct timespec));
|
||||
tsAdd(&time, state.frameTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.running = false;
|
||||
state.state = APP_STATE_SHUTDOWN;
|
||||
|
||||
if (t_cursor)
|
||||
lgJoinThread(t_cursor, NULL);
|
||||
@@ -239,7 +251,7 @@ static int cursorThread(void * unused)
|
||||
lgWaitEvent(e_startup, TIMEOUT_INFINITE);
|
||||
|
||||
// subscribe to the pointer queue
|
||||
while(state.running)
|
||||
while(state.state == APP_STATE_RUNNING)
|
||||
{
|
||||
status = lgmpClientSubscribe(state.lgmp, LGMP_Q_POINTER, &queue);
|
||||
if (status == LGMP_OK)
|
||||
@@ -252,11 +264,11 @@ static int cursorThread(void * unused)
|
||||
}
|
||||
|
||||
DEBUG_ERROR("lgmpClientSubscribe Failed: %s", lgmpStatusString(status));
|
||||
state.running = false;
|
||||
state.state = APP_STATE_SHUTDOWN;
|
||||
break;
|
||||
}
|
||||
|
||||
while(state.running)
|
||||
while(state.state == APP_STATE_RUNNING)
|
||||
{
|
||||
LGMPMessage msg;
|
||||
if ((status = lgmpClientProcess(queue, &msg)) != LGMP_OK)
|
||||
@@ -269,18 +281,25 @@ static int cursorThread(void * unused)
|
||||
state.lgr->on_mouse_event
|
||||
(
|
||||
state.lgrData,
|
||||
state.cursorVisible && state.drawCursor && state.cursorInView,
|
||||
state.cursorVisible && state.drawCursor,
|
||||
state.cursor.x,
|
||||
state.cursor.y
|
||||
);
|
||||
|
||||
lgSignalEvent(e_frame);
|
||||
}
|
||||
|
||||
usleep(params.cursorPollInterval);
|
||||
continue;
|
||||
}
|
||||
|
||||
DEBUG_ERROR("lgmpClientProcess Failed: %s", lgmpStatusString(status));
|
||||
state.running = false;
|
||||
if (status == LGMP_ERR_INVALID_SESSION)
|
||||
state.state = APP_STATE_RESTART;
|
||||
else
|
||||
{
|
||||
DEBUG_ERROR("lgmpClientProcess Failed: %s", lgmpStatusString(status));
|
||||
state.state = APP_STATE_SHUTDOWN;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -289,19 +308,6 @@ static int cursorThread(void * unused)
|
||||
state.cursorVisible =
|
||||
msg.udata & CURSOR_FLAG_VISIBLE;
|
||||
|
||||
if (msg.udata & CURSOR_FLAG_POSITION)
|
||||
{
|
||||
state.cursor.x = cursor->x;
|
||||
state.cursor.y = cursor->y;
|
||||
state.haveCursorPos = true;
|
||||
|
||||
if (!state.haveAligned && state.haveSrcSize && state.haveCurLocal)
|
||||
{
|
||||
alignMouseWithHost();
|
||||
state.haveAligned = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (msg.udata & CURSOR_FLAG_SHAPE)
|
||||
{
|
||||
switch(cursor->type)
|
||||
@@ -315,6 +321,9 @@ static int cursorThread(void * unused)
|
||||
continue;
|
||||
}
|
||||
|
||||
state.cursor.hx = cursor->hx;
|
||||
state.cursor.hy = cursor->hy;
|
||||
|
||||
const uint8_t * data = (const uint8_t *)(cursor + 1);
|
||||
if (!state.lgr->on_mouse_shape(
|
||||
state.lgrData,
|
||||
@@ -331,6 +340,16 @@ static int cursorThread(void * unused)
|
||||
}
|
||||
}
|
||||
|
||||
if (msg.udata & CURSOR_FLAG_POSITION)
|
||||
{
|
||||
state.cursor.x = cursor->x;
|
||||
state.cursor.y = cursor->y;
|
||||
state.haveCursorPos = true;
|
||||
|
||||
if (state.haveSrcSize && state.haveCurLocal && !state.serverMode)
|
||||
alignMouseWithGuest();
|
||||
}
|
||||
|
||||
lgmpClientMessageDone(queue);
|
||||
state.updateCursor = false;
|
||||
|
||||
@@ -341,10 +360,12 @@ static int cursorThread(void * unused)
|
||||
state.cursor.x,
|
||||
state.cursor.y
|
||||
);
|
||||
|
||||
if (params.mouseRedraw)
|
||||
lgSignalEvent(e_frame);
|
||||
}
|
||||
|
||||
lgmpClientUnsubscribe(&queue);
|
||||
state.running = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -355,11 +376,11 @@ static int frameThread(void * unused)
|
||||
|
||||
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
|
||||
lgWaitEvent(e_startup, TIMEOUT_INFINITE);
|
||||
if (!state.running)
|
||||
if (state.state != APP_STATE_RUNNING)
|
||||
return 0;
|
||||
|
||||
// subscribe to the frame queue
|
||||
while(state.running)
|
||||
while(state.state == APP_STATE_RUNNING)
|
||||
{
|
||||
status = lgmpClientSubscribe(state.lgmp, LGMP_Q_FRAME, &queue);
|
||||
if (status == LGMP_OK)
|
||||
@@ -372,11 +393,11 @@ static int frameThread(void * unused)
|
||||
}
|
||||
|
||||
DEBUG_ERROR("lgmpClientSubscribe Failed: %s", lgmpStatusString(status));
|
||||
state.running = false;
|
||||
state.state = APP_STATE_SHUTDOWN;
|
||||
break;
|
||||
}
|
||||
|
||||
while(state.running)
|
||||
while(state.state == APP_STATE_RUNNING)
|
||||
{
|
||||
LGMPMessage msg;
|
||||
if ((status = lgmpClientProcess(queue, &msg)) != LGMP_OK)
|
||||
@@ -387,7 +408,13 @@ static int frameThread(void * unused)
|
||||
continue;
|
||||
}
|
||||
|
||||
DEBUG_ERROR("lgmpClientProcess Failed: %s", lgmpStatusString(status));
|
||||
if (status == LGMP_ERR_INVALID_SESSION)
|
||||
state.state = APP_STATE_RESTART;
|
||||
else
|
||||
{
|
||||
DEBUG_ERROR("lgmpClientProcess Failed: %s", lgmpStatusString(status));
|
||||
state.state = APP_STATE_SHUTDOWN;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -427,6 +454,7 @@ static int frameThread(void * unused)
|
||||
if (error)
|
||||
{
|
||||
lgmpClientMessageDone(queue);
|
||||
state.state = APP_STATE_SHUTDOWN;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -445,33 +473,35 @@ static int frameThread(void * unused)
|
||||
if (!state.lgr->on_frame_event(state.lgrData, lgrFormat, fb))
|
||||
{
|
||||
DEBUG_ERROR("renderer on frame event returned failure");
|
||||
state.state = APP_STATE_SHUTDOWN;
|
||||
break;
|
||||
}
|
||||
lgmpClientMessageDone(queue);
|
||||
++state.frameCount;
|
||||
|
||||
lgSignalEvent(e_frame);
|
||||
atomic_fetch_add_explicit(&state.frameCount, 1, memory_order_relaxed);
|
||||
if (atomic_fetch_add_explicit(&a_framesPending, 1, memory_order_relaxed) == 0)
|
||||
lgSignalEvent(e_frame);
|
||||
|
||||
lgmpClientMessageDone(queue);
|
||||
}
|
||||
|
||||
lgmpClientUnsubscribe(&queue);
|
||||
state.running = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int spiceThread(void * arg)
|
||||
{
|
||||
while(state.running)
|
||||
while(state.state != APP_STATE_SHUTDOWN)
|
||||
if (!spice_process(1000))
|
||||
{
|
||||
if (state.running)
|
||||
if (state.state != APP_STATE_SHUTDOWN)
|
||||
{
|
||||
state.running = false;
|
||||
state.state = APP_STATE_SHUTDOWN;
|
||||
DEBUG_ERROR("failed to process spice messages");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
state.running = false;
|
||||
state.state = APP_STATE_SHUTDOWN;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -647,10 +677,22 @@ void spiceClipboardRequest(const SpiceDataType type)
|
||||
state.lgc->request(spice_type_to_clipboard_type(type));
|
||||
}
|
||||
|
||||
static void warpMouse(int x, int y)
|
||||
{
|
||||
if (state.warpState != WARP_STATE_ON)
|
||||
return;
|
||||
|
||||
state.warpFromX = state.curLastX;
|
||||
state.warpFromY = state.curLastY;
|
||||
state.warpToX = x;
|
||||
state.warpToY = y;
|
||||
state.warpState = WARP_STATE_ACTIVE;
|
||||
|
||||
SDL_WarpMouseInWindow(state.window, x, y);
|
||||
}
|
||||
|
||||
static void handleMouseMoveEvent(int ex, int ey)
|
||||
{
|
||||
static bool wrapping = false;
|
||||
static int wrapX, wrapY;
|
||||
|
||||
state.curLocalX = ex;
|
||||
state.curLocalY = ey;
|
||||
@@ -659,28 +701,23 @@ static void handleMouseMoveEvent(int ex, int ey)
|
||||
if (state.ignoreInput || !params.useSpiceInput)
|
||||
return;
|
||||
|
||||
if (state.warpState == WARP_STATE_ACTIVE)
|
||||
{
|
||||
if (ex == state.warpToX && ey == state.warpToY)
|
||||
{
|
||||
state.curLastX += state.warpToX - state.warpFromX;
|
||||
state.curLastY += state.warpToY - state.warpFromY;
|
||||
state.warpState = WARP_STATE_ON;
|
||||
}
|
||||
}
|
||||
|
||||
if (state.serverMode)
|
||||
{
|
||||
if (wrapping)
|
||||
if (
|
||||
ex < 100 || ex > state.windowW - 100 ||
|
||||
ey < 100 || ey > state.windowH - 100)
|
||||
{
|
||||
if (ex == state.windowW / 2 && ey == state.windowH / 2)
|
||||
{
|
||||
state.curLastX += (state.windowW / 2) - wrapX;
|
||||
state.curLastY += (state.windowH / 2) - wrapY;
|
||||
wrapping = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (
|
||||
ex < 100 || ex > state.windowW - 100 ||
|
||||
ey < 100 || ey > state.windowH - 100)
|
||||
{
|
||||
wrapping = true;
|
||||
wrapX = ex;
|
||||
wrapY = ey;
|
||||
SDL_WarpMouseInWindow(state.window, state.windowW / 2, state.windowH / 2);
|
||||
}
|
||||
warpMouse(state.windowW / 2, state.windowH / 2);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -692,6 +729,10 @@ static void handleMouseMoveEvent(int ex, int ey)
|
||||
{
|
||||
state.cursorInView = false;
|
||||
state.updateCursor = true;
|
||||
state.warpState = WARP_STATE_OFF;
|
||||
|
||||
if (params.useSpiceInput)
|
||||
state.drawCursor = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -700,6 +741,9 @@ static void handleMouseMoveEvent(int ex, int ey)
|
||||
{
|
||||
state.cursorInView = true;
|
||||
state.updateCursor = true;
|
||||
state.drawCursor = true;
|
||||
if (state.warpState == WARP_STATE_ARMED)
|
||||
state.warpState = WARP_STATE_ON;
|
||||
}
|
||||
|
||||
int rx = ex - state.curLastX;
|
||||
@@ -739,9 +783,9 @@ static void alignMouseWithGuest()
|
||||
if (state.ignoreInput || !params.useSpiceInput)
|
||||
return;
|
||||
|
||||
state.curLastX = (int)round((float)state.cursor.x / state.scaleX) + state.dstRect.x;
|
||||
state.curLastY = (int)round((float)state.cursor.y / state.scaleY) + state.dstRect.y;
|
||||
SDL_WarpMouseInWindow(state.window, state.curLastX, state.curLastY);
|
||||
state.curLastX = (int)round((float)(state.cursor.x + state.cursor.hx) / state.scaleX) + state.dstRect.x;
|
||||
state.curLastY = (int)round((float)(state.cursor.y + state.cursor.hy) / state.scaleY) + state.dstRect.y;
|
||||
warpMouse(state.curLastX, state.curLastY);
|
||||
}
|
||||
|
||||
static void alignMouseWithHost()
|
||||
@@ -752,8 +796,8 @@ static void alignMouseWithHost()
|
||||
if (!state.haveCursorPos || state.serverMode)
|
||||
return;
|
||||
|
||||
state.curLastX = (int)round((float)state.cursor.x / state.scaleX) + state.dstRect.x;
|
||||
state.curLastY = (int)round((float)state.cursor.y / state.scaleY) + state.dstRect.y;
|
||||
state.curLastX = (int)round((float)(state.cursor.x + state.cursor.hx) / state.scaleX) + state.dstRect.x;
|
||||
state.curLastY = (int)round((float)(state.cursor.y + state.cursor.hy) / state.scaleY) + state.dstRect.y;
|
||||
handleMouseMoveEvent(state.curLocalX, state.curLocalY);
|
||||
}
|
||||
|
||||
@@ -775,6 +819,7 @@ static void handleWindowLeave()
|
||||
state.drawCursor = false;
|
||||
state.cursorInView = false;
|
||||
state.updateCursor = true;
|
||||
state.warpState = WARP_STATE_OFF;
|
||||
}
|
||||
|
||||
static void handleWindowEnter()
|
||||
@@ -785,6 +830,7 @@ static void handleWindowEnter()
|
||||
alignMouseWithHost();
|
||||
state.drawCursor = true;
|
||||
state.updateCursor = true;
|
||||
state.warpState = WARP_STATE_ARMED;
|
||||
}
|
||||
|
||||
int eventFilter(void * userdata, SDL_Event * event)
|
||||
@@ -796,7 +842,7 @@ int eventFilter(void * userdata, SDL_Event * event)
|
||||
if (!params.ignoreQuit)
|
||||
{
|
||||
DEBUG_INFO("Quit event received, exiting...");
|
||||
state.running = false;
|
||||
state.state = APP_STATE_SHUTDOWN;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -823,7 +869,7 @@ int eventFilter(void * userdata, SDL_Event * event)
|
||||
|
||||
// allow a window close event to close the application even if ignoreQuit is set
|
||||
case SDL_WINDOWEVENT_CLOSE:
|
||||
state.running = false;
|
||||
state.state = APP_STATE_SHUTDOWN;
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
@@ -919,7 +965,6 @@ int eventFilter(void * userdata, SDL_Event * event)
|
||||
if (params.useSpiceInput)
|
||||
{
|
||||
state.serverMode = !state.serverMode;
|
||||
spice_mouse_mode(state.serverMode);
|
||||
SDL_SetWindowGrab(state.window, state.serverMode);
|
||||
DEBUG_INFO("Server Mode: %s", state.serverMode ? "on" : "off");
|
||||
|
||||
@@ -928,7 +973,9 @@ int eventFilter(void * userdata, SDL_Event * event)
|
||||
state.serverMode ? "Capture Enabled" : "Capture Disabled"
|
||||
);
|
||||
|
||||
if (!state.serverMode)
|
||||
if (state.serverMode)
|
||||
state.warpState = WARP_STATE_ON;
|
||||
else
|
||||
alignMouseWithGuest();
|
||||
}
|
||||
}
|
||||
@@ -1024,7 +1071,7 @@ void int_handler(int signal)
|
||||
case SIGINT:
|
||||
case SIGTERM:
|
||||
DEBUG_INFO("Caught signal, shutting down...");
|
||||
state.running = false;
|
||||
state.state = APP_STATE_SHUTDOWN;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1072,7 +1119,7 @@ static void toggle_input(SDL_Scancode key, void * opaque)
|
||||
|
||||
static void quit(SDL_Scancode key, void * opaque)
|
||||
{
|
||||
state.running = false;
|
||||
state.state = APP_STATE_SHUTDOWN;
|
||||
}
|
||||
|
||||
static void mouse_sens_inc(SDL_Scancode key, void * opaque)
|
||||
@@ -1155,7 +1202,7 @@ static void release_key_binds()
|
||||
static int lg_run()
|
||||
{
|
||||
memset(&state, 0, sizeof(state));
|
||||
state.running = true;
|
||||
state.state = APP_STATE_RUNNING;
|
||||
state.scaleX = 1.0f;
|
||||
state.scaleY = 1.0f;
|
||||
state.resizeDone = true;
|
||||
@@ -1218,14 +1265,15 @@ static int lg_run()
|
||||
return -1;
|
||||
}
|
||||
|
||||
while(state.running && !spice_ready())
|
||||
while(state.state != APP_STATE_SHUTDOWN && !spice_ready())
|
||||
if (!spice_process(1000))
|
||||
{
|
||||
state.running = false;
|
||||
state.state = APP_STATE_SHUTDOWN;
|
||||
DEBUG_ERROR("Failed to process spice messages");
|
||||
return -1;
|
||||
}
|
||||
|
||||
spice_mouse_mode(true);
|
||||
if (!lgCreateThread("spiceThread", spiceThread, NULL, &t_spice))
|
||||
{
|
||||
DEBUG_ERROR("spice create thread failed");
|
||||
@@ -1310,13 +1358,15 @@ static int lg_run()
|
||||
// ensure renderer viewport is aware of the current window size
|
||||
updatePositionInfo();
|
||||
|
||||
// use a default of 60FPS now that frame updates are host update triggered
|
||||
if (params.fpsMin == -1)
|
||||
state.frameTime = 1e9 / 60;
|
||||
{
|
||||
// minimum 60fps to keep interactivity decent
|
||||
state.frameTime = 1000000000ULL / 60ULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_INFO("Using the FPS minimum from args: %d", params.fpsMin);
|
||||
state.frameTime = 1e9 / params.fpsMin;
|
||||
state.frameTime = 1000000000ULL / (unsigned long long)params.fpsMin;
|
||||
}
|
||||
|
||||
register_key_binds();
|
||||
@@ -1394,6 +1444,13 @@ static int lg_run()
|
||||
SDL_ShowCursor(SDL_DISABLE);
|
||||
}
|
||||
|
||||
if (params.captureOnStart)
|
||||
{
|
||||
state.serverMode = true;
|
||||
SDL_SetWindowGrab(state.window, state.serverMode);
|
||||
DEBUG_INFO("Server Mode: %s", state.serverMode ? "on" : "off");
|
||||
}
|
||||
|
||||
// setup the startup condition
|
||||
if (!(e_startup = lgCreateEvent(false, 0)))
|
||||
{
|
||||
@@ -1425,7 +1482,7 @@ static int lg_run()
|
||||
|
||||
LGMP_STATUS status;
|
||||
|
||||
while(state.running)
|
||||
while(state.state == APP_STATE_RUNNING)
|
||||
{
|
||||
if ((status = lgmpClientInit(state.shm.mem, state.shm.size, &state.lgmp)) == LGMP_OK)
|
||||
break;
|
||||
@@ -1440,9 +1497,10 @@ static int lg_run()
|
||||
|
||||
uint32_t udataSize;
|
||||
KVMFR *udata;
|
||||
|
||||
int waitCount = 0;
|
||||
while(state.running)
|
||||
|
||||
restart:
|
||||
while(state.state == APP_STATE_RUNNING)
|
||||
{
|
||||
if ((status = lgmpClientSessionInit(state.lgmp, &udataSize, (uint8_t **)&udata)) == LGMP_OK)
|
||||
break;
|
||||
@@ -1471,22 +1529,35 @@ static int lg_run()
|
||||
SDL_WaitEventTimeout(NULL, 1000);
|
||||
}
|
||||
|
||||
if (!state.running)
|
||||
if (state.state != APP_STATE_RUNNING)
|
||||
return -1;
|
||||
|
||||
if (udataSize != sizeof(KVMFR) ||
|
||||
memcmp(udata->magic, KVMFR_MAGIC, sizeof(udata->magic)) != 0 ||
|
||||
udata->version != KVMFR_VERSION)
|
||||
// dont show warnings again after the first startup
|
||||
waitCount = 100;
|
||||
|
||||
const bool magicMatches = memcmp(udata->magic, KVMFR_MAGIC, sizeof(udata->magic)) == 0;
|
||||
if (udataSize != sizeof(KVMFR) || !magicMatches || udata->version != KVMFR_VERSION)
|
||||
{
|
||||
DEBUG_BREAK();
|
||||
DEBUG_ERROR("The host application is not compatible with this client");
|
||||
DEBUG_ERROR("Expected KVMFR version %d", KVMFR_VERSION);
|
||||
DEBUG_ERROR("This is not a Looking Glass error, do not report this");
|
||||
DEBUG_ERROR("Please install the matching host application for this client");
|
||||
|
||||
if (magicMatches)
|
||||
{
|
||||
DEBUG_ERROR("Expected KVMFR version %d, got %d", KVMFR_VERSION, udata->version);
|
||||
if (udata->version >= 2)
|
||||
DEBUG_ERROR("Host version: %s", udata->hostver);
|
||||
}
|
||||
else
|
||||
DEBUG_ERROR("Invalid KVMFR magic");
|
||||
|
||||
DEBUG_BREAK();
|
||||
return -1;
|
||||
}
|
||||
|
||||
DEBUG_INFO("Host ready, starting session");
|
||||
DEBUG_INFO("Host ready, reported version: %s", udata->hostver);
|
||||
DEBUG_INFO("Starting session");
|
||||
|
||||
if (!lgCreateThread("cursorThread", cursorThread, NULL, &t_cursor))
|
||||
{
|
||||
@@ -1500,14 +1571,30 @@ static int lg_run()
|
||||
return -1;
|
||||
}
|
||||
|
||||
while(state.running)
|
||||
while(state.state == APP_STATE_RUNNING)
|
||||
{
|
||||
if (!lgmpClientSessionValid(state.lgmp))
|
||||
{
|
||||
DEBUG_WARN("Session is invalid, has the host shutdown?");
|
||||
state.state = APP_STATE_RESTART;
|
||||
break;
|
||||
}
|
||||
SDL_WaitEventTimeout(NULL, 1000);
|
||||
SDL_WaitEventTimeout(NULL, 100);
|
||||
}
|
||||
|
||||
if (state.state == APP_STATE_RESTART)
|
||||
{
|
||||
lgSignalEvent(e_startup);
|
||||
lgSignalEvent(e_frame);
|
||||
lgJoinThread(t_frame , NULL);
|
||||
lgJoinThread(t_cursor, NULL);
|
||||
t_frame = NULL;
|
||||
t_cursor = NULL;
|
||||
|
||||
state.state = APP_STATE_RUNNING;
|
||||
state.lgr->on_restart(state.lgrData);
|
||||
|
||||
DEBUG_INFO("Waiting for the host to restart...");
|
||||
goto restart;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -1515,8 +1602,7 @@ static int lg_run()
|
||||
|
||||
static void lg_shutdown()
|
||||
{
|
||||
state.running = false;
|
||||
|
||||
state.state = APP_STATE_SHUTDOWN;
|
||||
if (t_render)
|
||||
{
|
||||
lgSignalEvent(e_startup);
|
||||
@@ -1580,6 +1666,12 @@ static void lg_shutdown()
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
if (getuid() == 0)
|
||||
{
|
||||
DEBUG_ERROR("Do not run looking glass as root!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
DEBUG_INFO("Looking Glass (" BUILD_VERSION ")");
|
||||
DEBUG_INFO("Locking Method: " LG_LOCK_MODE);
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdatomic.h>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include "interface/app.h"
|
||||
@@ -28,9 +29,30 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include "spice/spice.h"
|
||||
#include <lgmp/client.h>
|
||||
|
||||
enum RunState
|
||||
{
|
||||
APP_STATE_RUNNING,
|
||||
APP_STATE_RESTART,
|
||||
APP_STATE_SHUTDOWN
|
||||
};
|
||||
|
||||
struct CursorInfo
|
||||
{
|
||||
int x , y;
|
||||
int hx, hy;
|
||||
};
|
||||
|
||||
enum WarpState
|
||||
{
|
||||
WARP_STATE_ARMED,
|
||||
WARP_STATE_ON,
|
||||
WARP_STATE_ACTIVE,
|
||||
WARP_STATE_OFF
|
||||
};
|
||||
|
||||
struct AppState
|
||||
{
|
||||
bool running;
|
||||
enum RunState state;
|
||||
bool ignoreInput;
|
||||
bool escapeActive;
|
||||
SDL_Scancode escapeAction;
|
||||
@@ -41,7 +63,7 @@ struct AppState
|
||||
int windowW, windowH;
|
||||
SDL_Point srcSize;
|
||||
LG_RendererRect dstRect;
|
||||
SDL_Point cursor;
|
||||
struct CursorInfo cursor;
|
||||
bool cursorVisible;
|
||||
|
||||
bool serverMode;
|
||||
@@ -59,6 +81,10 @@ struct AppState
|
||||
int curLocalY;
|
||||
bool haveAligned;
|
||||
|
||||
enum WarpState warpState;
|
||||
int warpFromX, warpFromY;
|
||||
int warpToX , warpToY;
|
||||
|
||||
const LG_Renderer * lgr;
|
||||
void * lgrData;
|
||||
bool lgrResize;
|
||||
@@ -75,11 +101,11 @@ struct AppState
|
||||
PLGMPClientQueue frameQueue;
|
||||
PLGMPClientQueue pointerQueue;
|
||||
|
||||
uint64_t frameTime;
|
||||
uint64_t lastFrameTime;
|
||||
uint64_t renderTime;
|
||||
uint64_t frameCount;
|
||||
uint64_t renderCount;
|
||||
atomic_uint_least64_t frameTime;
|
||||
uint64_t lastFrameTime;
|
||||
uint64_t renderTime;
|
||||
uint64_t frameCount;
|
||||
uint64_t renderCount;
|
||||
|
||||
|
||||
uint64_t resizeTimeout;
|
||||
@@ -124,6 +150,7 @@ struct AppParams
|
||||
bool grabKeyboard;
|
||||
SDL_Scancode escapeKey;
|
||||
bool showAlerts;
|
||||
bool captureOnStart;
|
||||
|
||||
unsigned int cursorPollInterval;
|
||||
unsigned int framePollInterval;
|
||||
@@ -133,6 +160,7 @@ struct AppParams
|
||||
|
||||
const char * windowTitle;
|
||||
int mouseSens;
|
||||
bool mouseRedraw;
|
||||
};
|
||||
|
||||
struct CBRequest
|
||||
|
||||
@@ -5,6 +5,8 @@ include_directories(
|
||||
${PROJECT_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
add_definitions(-D_GNU_SOURCE)
|
||||
|
||||
if(ENABLE_BACKTRACE)
|
||||
add_definitions(-DENABLE_BACKTRACE)
|
||||
endif()
|
||||
|
||||
@@ -51,12 +51,13 @@ typedef enum CursorType
|
||||
CursorType;
|
||||
|
||||
#define KVMFR_MAGIC "KVMFR---"
|
||||
#define KVMFR_VERSION 1
|
||||
#define KVMFR_VERSION 3
|
||||
|
||||
typedef struct KVMFR
|
||||
{
|
||||
char magic[8];
|
||||
uint32_t version;
|
||||
char hostver[32];
|
||||
}
|
||||
KVMFR;
|
||||
|
||||
@@ -64,6 +65,7 @@ typedef struct KVMFRCursor
|
||||
{
|
||||
int16_t x, y; // cursor x & y position
|
||||
CursorType type; // shape buffer data type
|
||||
int8_t hx, hy; // shape hotspot x & y
|
||||
uint32_t width; // width of the shape
|
||||
uint32_t height; // height of the shape
|
||||
uint32_t pitch; // row length in bytes of the shape
|
||||
|
||||
@@ -53,7 +53,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
sizeof(s) > 20 && (s)[sizeof(s)-21] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 20 : \
|
||||
sizeof(s) > 21 && (s)[sizeof(s)-22] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 21 : (s))
|
||||
|
||||
#define DEBUG_PRINT(type, fmt, ...) do {fprintf(stderr, "%" PRId64 " " type " %20s:%-4u | %-30s | " fmt "\n", microtime(), STRIPPATH(__FILE__), __LINE__, __FUNCTION__, ##__VA_ARGS__);} while (0)
|
||||
#define DEBUG_PRINT(type, fmt, ...) do {fprintf(stderr, "%12" PRId64 " " type " %20s:%-4u | %-30s | " fmt "\n", microtime(), STRIPPATH(__FILE__), __LINE__, __FUNCTION__, ##__VA_ARGS__);} while (0)
|
||||
|
||||
#define DEBUG_BREAK() DEBUG_PRINT("[ ]", "%s", "================================================================================")
|
||||
#define DEBUG_INFO(fmt, ...) DEBUG_PRINT("[I]", fmt, ##__VA_ARGS__)
|
||||
|
||||
@@ -22,8 +22,12 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
#include <string.h>
|
||||
#include <stdatomic.h>
|
||||
#include <emmintrin.h>
|
||||
#include <smmintrin.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define FB_CHUNK_SIZE 1024
|
||||
#define FB_CHUNK_SIZE 1048576 // 1MB
|
||||
#define FB_SPIN_LIMIT 10000 // 10ms
|
||||
|
||||
struct stFrameBuffer
|
||||
{
|
||||
@@ -35,31 +39,63 @@ const size_t FrameBufferStructSize = sizeof(FrameBuffer);
|
||||
|
||||
void framebuffer_wait(const FrameBuffer * frame, size_t size)
|
||||
{
|
||||
while(atomic_load_explicit(&frame->wp, memory_order_relaxed) != size) {}
|
||||
while(atomic_load_explicit(&frame->wp, memory_order_acquire) != size) {}
|
||||
}
|
||||
|
||||
|
||||
bool framebuffer_read(const FrameBuffer * frame, void * dst, size_t dstpitch,
|
||||
size_t height, size_t width, size_t bpp, size_t pitch)
|
||||
bool framebuffer_read(const FrameBuffer * frame, void * restrict dst,
|
||||
size_t dstpitch, size_t height, size_t width, size_t bpp, size_t pitch)
|
||||
{
|
||||
uint8_t *d = (uint8_t*)dst;
|
||||
uint8_t * restrict d = (uint8_t*)dst;
|
||||
uint_least32_t rp = 0;
|
||||
size_t y = 0;
|
||||
const size_t linewidth = width * bpp;
|
||||
const size_t blocks = linewidth / 64;
|
||||
const size_t left = linewidth % 64;
|
||||
|
||||
while(y < height)
|
||||
{
|
||||
uint_least32_t wp;
|
||||
int spinCount = 0;
|
||||
|
||||
/* spinlock */
|
||||
do
|
||||
wp = atomic_load_explicit(&frame->wp, memory_order_relaxed);
|
||||
while(wp - rp < pitch);
|
||||
wp = atomic_load_explicit(&frame->wp, memory_order_acquire);
|
||||
while(wp - rp < linewidth)
|
||||
{
|
||||
if (++spinCount == FB_SPIN_LIMIT)
|
||||
return false;
|
||||
|
||||
memcpy(d, frame->data + rp, linewidth);
|
||||
usleep(1);
|
||||
wp = atomic_load_explicit(&frame->wp, memory_order_acquire);
|
||||
}
|
||||
|
||||
_mm_mfence();
|
||||
__m128i * restrict s = (__m128i *)(frame->data + rp);
|
||||
for(int i = 0; i < blocks; ++i)
|
||||
{
|
||||
__m128i *_d = (__m128i *)d;
|
||||
__m128i *_s = (__m128i *)s;
|
||||
__m128i v1 = _mm_stream_load_si128(_s + 0);
|
||||
__m128i v2 = _mm_stream_load_si128(_s + 1);
|
||||
__m128i v3 = _mm_stream_load_si128(_s + 2);
|
||||
__m128i v4 = _mm_stream_load_si128(_s + 3);
|
||||
|
||||
_mm_storeu_si128(_d + 0, v1);
|
||||
_mm_storeu_si128(_d + 1, v2);
|
||||
_mm_storeu_si128(_d + 2, v3);
|
||||
_mm_storeu_si128(_d + 3, v4);
|
||||
|
||||
d += 64;
|
||||
s += 4;
|
||||
}
|
||||
|
||||
if (left)
|
||||
{
|
||||
memcpy(d, s, left);
|
||||
d += left;
|
||||
}
|
||||
|
||||
rp += pitch;
|
||||
d += dstpitch;
|
||||
d += dstpitch - linewidth;
|
||||
++y;
|
||||
}
|
||||
|
||||
@@ -76,11 +112,18 @@ bool framebuffer_read_fn(const FrameBuffer * frame, size_t height, size_t width,
|
||||
while(y < height)
|
||||
{
|
||||
uint_least32_t wp;
|
||||
int spinCount = 0;
|
||||
|
||||
/* spinlock */
|
||||
do
|
||||
wp = atomic_load_explicit(&frame->wp, memory_order_relaxed);
|
||||
while(wp - rp < pitch);
|
||||
wp = atomic_load_explicit(&frame->wp, memory_order_acquire);
|
||||
while(wp - rp < linewidth)
|
||||
{
|
||||
if (++spinCount == FB_SPIN_LIMIT)
|
||||
return false;
|
||||
|
||||
usleep(1);
|
||||
wp = atomic_load_explicit(&frame->wp, memory_order_acquire);
|
||||
}
|
||||
|
||||
if (!fn(opaque, frame->data + rp, linewidth))
|
||||
return false;
|
||||
@@ -97,18 +140,47 @@ bool framebuffer_read_fn(const FrameBuffer * frame, size_t height, size_t width,
|
||||
*/
|
||||
void framebuffer_prepare(FrameBuffer * frame)
|
||||
{
|
||||
atomic_store(&frame->wp, 0);
|
||||
atomic_store_explicit(&frame->wp, 0, memory_order_release);
|
||||
}
|
||||
|
||||
bool framebuffer_write(FrameBuffer * frame, const void * src, size_t size)
|
||||
bool framebuffer_write(FrameBuffer * frame, const void * restrict src, size_t size)
|
||||
{
|
||||
__m128i * restrict s = (__m128i *)src;
|
||||
__m128i * restrict d = (__m128i *)frame->data;
|
||||
size_t wp = 0;
|
||||
|
||||
_mm_mfence();
|
||||
|
||||
/* copy in chunks */
|
||||
while(size)
|
||||
while(size > 63)
|
||||
{
|
||||
size_t copy = size < FB_CHUNK_SIZE ? FB_CHUNK_SIZE : size;
|
||||
memcpy(frame->data + frame->wp, src, copy);
|
||||
atomic_fetch_add(&frame->wp, copy);
|
||||
size -= copy;
|
||||
__m128i *_d = (__m128i *)d;
|
||||
__m128i *_s = (__m128i *)s;
|
||||
__m128i v1 = _mm_stream_load_si128(_s + 0);
|
||||
__m128i v2 = _mm_stream_load_si128(_s + 1);
|
||||
__m128i v3 = _mm_stream_load_si128(_s + 2);
|
||||
__m128i v4 = _mm_stream_load_si128(_s + 3);
|
||||
|
||||
_mm_store_si128(_d + 0, v1);
|
||||
_mm_store_si128(_d + 1, v2);
|
||||
_mm_store_si128(_d + 2, v3);
|
||||
_mm_store_si128(_d + 3, v4);
|
||||
|
||||
s += 4;
|
||||
d += 4;
|
||||
size -= 64;
|
||||
wp += 64;
|
||||
|
||||
if (wp % FB_CHUNK_SIZE == 0)
|
||||
atomic_store_explicit(&frame->wp, wp, memory_order_release);
|
||||
}
|
||||
|
||||
if(size)
|
||||
{
|
||||
memcpy(frame->data + wp, s, size);
|
||||
wp += size;
|
||||
}
|
||||
|
||||
atomic_store_explicit(&frame->wp, wp, memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ add_library(lg_common_platform_code STATIC
|
||||
thread.c
|
||||
event.c
|
||||
ivshmem.c
|
||||
time.c
|
||||
)
|
||||
|
||||
if(ENABLE_BACKTRACE)
|
||||
@@ -20,4 +21,5 @@ endif()
|
||||
target_link_libraries(lg_common_platform_code
|
||||
lg_common
|
||||
pthread
|
||||
rt
|
||||
)
|
||||
|
||||
@@ -17,7 +17,6 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include "common/crash.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
|
||||
@@ -83,19 +83,20 @@ bool lgWaitEventAbs(LGEvent * handle, struct timespec * ts)
|
||||
}
|
||||
|
||||
bool ret = true;
|
||||
int res;
|
||||
while(ret && !atomic_load(&handle->flag))
|
||||
{
|
||||
if (!ts)
|
||||
{
|
||||
if (pthread_cond_wait(&handle->cond, &handle->mutex) != 0)
|
||||
if ((res = pthread_cond_wait(&handle->cond, &handle->mutex)) != 0)
|
||||
{
|
||||
DEBUG_ERROR("Wait to wait on the condition");
|
||||
DEBUG_ERROR("Failed to wait on the condition (err: %d)", res);
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch(pthread_cond_timedwait(&handle->cond, &handle->mutex, ts))
|
||||
switch((res = pthread_cond_timedwait(&handle->cond, &handle->mutex, ts)))
|
||||
{
|
||||
case 0:
|
||||
break;
|
||||
@@ -106,7 +107,7 @@ bool lgWaitEventAbs(LGEvent * handle, struct timespec * ts)
|
||||
|
||||
default:
|
||||
ret = false;
|
||||
DEBUG_ERROR("Timed wait failed");
|
||||
DEBUG_ERROR("Timed wait failed (err: %d)", res);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -145,6 +146,9 @@ bool lgWaitEventNS(LGEvent * handle, unsigned int timeout)
|
||||
|
||||
bool lgWaitEvent(LGEvent * handle, unsigned int timeout)
|
||||
{
|
||||
if (timeout == TIMEOUT_INFINITE)
|
||||
return lgWaitEventAbs(handle, NULL);
|
||||
|
||||
return lgWaitEventNS(handle, timeout * 1000000);
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,8 @@ bool lgCreateThread(const char * name, LGThreadFunction function, void * opaque,
|
||||
*handle = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
pthread_setname_np((*handle)->handle, name);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -71,4 +73,4 @@ bool lgJoinThread(LGThread * handle, int * resultCode)
|
||||
|
||||
free(handle);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
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')
|
||||
buffer[i] = 0;
|
||||
|
||||
fprintf(stderr, "[E] %20s:%-4u | %-30s | %s: 0x%08x (%s)\n", file, line, function, desc, (int)status, buffer);
|
||||
fprintf(stderr, "%12" PRId64 " [E] %20s:%-4u | %-30s | %s: 0x%08x (%s)\n", microtime(), file, line, function, desc, (int)status, buffer);
|
||||
LocalFree(buffer);
|
||||
}
|
||||
|
||||
@@ -63,4 +63,4 @@ bool IsWindows8()
|
||||
return
|
||||
(CompareWindowsVersion(6, 3) == TRUE) ||
|
||||
(CompareWindowsVersion(6, 2) == TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
```
|
||||
mkdir LookingGlass/c-host/build
|
||||
cd LookingGlass/c-host/build
|
||||
mkdir LookingGlass/host/build
|
||||
cd LookingGlass/host/build
|
||||
cmake -G "MSYS Makefiles" ..
|
||||
make
|
||||
```
|
||||
@@ -44,17 +44,46 @@ cmake -DCMAKE_TOOLCHAIN_FILE=../toolchain-mingw64.cmake ..
|
||||
make
|
||||
```
|
||||
|
||||
## Building the Windows installer
|
||||
|
||||
Install NSIS compiler
|
||||
Build the host program, see above sections.
|
||||
Build installer with `makensis platform/Windows/installer.nsi`
|
||||
The resulting installer will be at
|
||||
`platform/Windows/looking-glass-host-setup.exe`
|
||||
|
||||
## Where is the log?
|
||||
|
||||
It is in your user's temp directory:
|
||||
|
||||
%TEMP%\looking-glass-host.txt
|
||||
|
||||
For example:
|
||||
Or if running as a system service it will be located in:
|
||||
|
||||
C:\Users\YourUser\AppData\Local\Temp\looking-glass-host.txt
|
||||
C:\Windows\Temp\looking-glass-host.txt
|
||||
|
||||
You can also open it by simply right clicking the tray icon and selecting "Open Log File"
|
||||
You can find out where the file is by right clicking on the tray icon and
|
||||
selecting "Log File Location"
|
||||
|
||||
### High priority capture using DXGI and Secure Desktop (UAC) capture support
|
||||
|
||||
By default Windows gives priority to the foreground application for any GPU
|
||||
work which causes issues with capture if the foreground application is consuming
|
||||
100% of the available GPU resources. The looking glass host application is able
|
||||
to increase the kernel GPU thread to realtime priority which fixes this, but in
|
||||
order to do so it must run as the `SYSTEM` user account. To do this, Looking
|
||||
Glass needs to run as a service. This can be accomplished by either using the
|
||||
NSIS installer which will do this for you, or you can use the following command
|
||||
to Install the service manually:
|
||||
|
||||
looking-glass-host.exe InstallService
|
||||
|
||||
To remove the service use the following command:
|
||||
|
||||
looking-glass-host.exe UninstallService
|
||||
|
||||
This will also enable the host application to capture the secure desktop which
|
||||
includes things like the lock screen and UAC prompts.
|
||||
|
||||
## Why does this version require Administrator privileges
|
||||
|
||||
@@ -67,6 +67,7 @@ typedef struct CapturePointer
|
||||
|
||||
bool shapeUpdate;
|
||||
CaptureFormat format;
|
||||
unsigned int hx, hy;
|
||||
unsigned int width, height;
|
||||
unsigned int pitch;
|
||||
}
|
||||
@@ -27,3 +27,4 @@ void app_quit();
|
||||
|
||||
// these must be implemented for each OS
|
||||
const char * os_getExecutable();
|
||||
const char * os_getDataPath();
|
||||
@@ -30,6 +30,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
struct app
|
||||
{
|
||||
const char * executable;
|
||||
char * dataPath;
|
||||
};
|
||||
|
||||
struct app app = { 0 };
|
||||
@@ -37,7 +38,13 @@ struct app app = { 0 };
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
app.executable = argv[0];
|
||||
|
||||
struct passwd * pw = getpwuid(getuid());
|
||||
alloc_sprintf(&app.dataPath, "%s/", pw->pw_dir);
|
||||
|
||||
int result = app_main(argc, argv);
|
||||
|
||||
free(app.dataPath);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -56,4 +63,9 @@ bool app_init()
|
||||
const char * os_getExecutable()
|
||||
{
|
||||
return app.executable;
|
||||
}
|
||||
}
|
||||
|
||||
const char * os_getDataPath()
|
||||
{
|
||||
return app.dataPath;
|
||||
}
|
||||
@@ -7,6 +7,7 @@ include_directories(
|
||||
|
||||
add_library(platform_Windows STATIC
|
||||
src/platform.c
|
||||
src/service.c
|
||||
src/mousehook.c
|
||||
)
|
||||
|
||||
@@ -23,9 +24,19 @@ target_link_libraries(platform_Windows
|
||||
"${PROJECT_BINARY_DIR}/resource.o"
|
||||
lg_common
|
||||
capture
|
||||
|
||||
userenv
|
||||
wtsapi32
|
||||
psapi
|
||||
)
|
||||
|
||||
target_include_directories(platform_Windows
|
||||
PRIVATE
|
||||
src
|
||||
)
|
||||
|
||||
# these are for the nsis installer generator
|
||||
configure_file("${PROJECT_SOURCE_DIR}/installer.nsi" "${PROJECT_BINARY_DIR}/installer.nsi" COPYONLY)
|
||||
configure_file("${PROJECT_TOP}/resources/icon.ico" "${PROJECT_BINARY_DIR}/icon.ico" COPYONLY)
|
||||
configure_file("${PROJECT_TOP}/VERSION" "${PROJECT_BINARY_DIR}/VERSION" COPYONLY)
|
||||
configure_file("${PROJECT_TOP}/LICENSE" "${PROJECT_BINARY_DIR}/LICENSE.txt" COPYONLY)
|
||||
@@ -26,12 +26,26 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include "common/event.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdatomic.h>
|
||||
#include <unistd.h>
|
||||
#include <dxgi.h>
|
||||
#include <d3d11.h>
|
||||
#include <d3dcommon.h>
|
||||
|
||||
#include "dxgi_extra.h"
|
||||
|
||||
typedef enum _D3DKMT_SCHEDULINGPRIORITYCLASS
|
||||
{
|
||||
D3DKMT_SCHEDULINGPRIORITYCLASS_IDLE,
|
||||
D3DKMT_SCHEDULINGPRIORITYCLASS_BELOW_NORMAL,
|
||||
D3DKMT_SCHEDULINGPRIORITYCLASS_NORMAL,
|
||||
D3DKMT_SCHEDULINGPRIORITYCLASS_ABOVE_NORMAL,
|
||||
D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH,
|
||||
D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME
|
||||
}
|
||||
D3DKMT_SCHEDULINGPRIORITYCLASS;
|
||||
typedef NTSTATUS WINAPI (*PD3DKMTSetProcessSchedulingPriorityClass)(HANDLE, D3DKMT_SCHEDULINGPRIORITYCLASS);
|
||||
|
||||
#define LOCKED(x) INTERLOCKED_SECTION(this->deviceContextLock, x)
|
||||
|
||||
enum TextureState
|
||||
@@ -43,7 +57,7 @@ enum TextureState
|
||||
|
||||
typedef struct Texture
|
||||
{
|
||||
enum TextureState state;
|
||||
volatile enum TextureState state;
|
||||
ID3D11Texture2D * tex;
|
||||
D3D11_MAPPED_SUBRESOURCE map;
|
||||
}
|
||||
@@ -70,7 +84,7 @@ struct iface
|
||||
Texture * texture;
|
||||
int texRIndex;
|
||||
int texWIndex;
|
||||
volatile int texReady;
|
||||
atomic_int texReady;
|
||||
bool needsRelease;
|
||||
|
||||
CaptureGetPointerBuffer getPointerBufferFn;
|
||||
@@ -130,9 +144,9 @@ static void dxgi_initOptions()
|
||||
{
|
||||
.module = "dxgi",
|
||||
.name = "useAcquireLock",
|
||||
.description = "Enable locking around `AcquireFrame` (use if freezing, may lower performance)",
|
||||
.description = "Enable locking around `AcquireFrame` (EXPERIMENTAL, leave enabled if you're not sure!)",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false
|
||||
.value.x_bool = true
|
||||
},
|
||||
{0}
|
||||
};
|
||||
@@ -190,8 +204,8 @@ static bool dxgi_init()
|
||||
{
|
||||
DEBUG_INFO("The above error(s) will prevent LG from being able to capture the secure desktop (UAC dialogs)");
|
||||
DEBUG_INFO("This is not a failure, please do not report this as an issue.");
|
||||
DEBUG_INFO("To fix this run LG using the PsExec SysInternals tool from Microsoft.");
|
||||
DEBUG_INFO("https://docs.microsoft.com/en-us/sysinternals/downloads/psexec");
|
||||
DEBUG_INFO("To fix this, install and run the Looking Glass host as a service.");
|
||||
DEBUG_INFO("looking-glass-host.exe InstallService");
|
||||
}
|
||||
|
||||
// this is required for DXGI 1.5 support to function
|
||||
@@ -216,9 +230,9 @@ static bool dxgi_init()
|
||||
this->stop = false;
|
||||
this->texRIndex = 0;
|
||||
this->texWIndex = 0;
|
||||
this->texReady = 0;
|
||||
atomic_store(&this->texReady, 0);
|
||||
|
||||
lgResetEvent(this->frameEvent );
|
||||
lgResetEvent(this->frameEvent);
|
||||
|
||||
status = CreateDXGIFactory1(&IID_IDXGIFactory1, (void **)&this->factory);
|
||||
if (FAILED(status))
|
||||
@@ -385,6 +399,25 @@ static bool dxgi_init()
|
||||
|
||||
// bump up our priority
|
||||
{
|
||||
HMODULE gdi32 = GetModuleHandleA("GDI32");
|
||||
if (gdi32)
|
||||
{
|
||||
PD3DKMTSetProcessSchedulingPriorityClass fn =
|
||||
(PD3DKMTSetProcessSchedulingPriorityClass)GetProcAddress(gdi32, "D3DKMTSetProcessSchedulingPriorityClass");
|
||||
|
||||
if (fn)
|
||||
{
|
||||
status = fn(GetCurrentProcess(), D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME);
|
||||
if (FAILED(status))
|
||||
{
|
||||
DEBUG_WARN("Failed to set realtime GPU priority.");
|
||||
DEBUG_INFO("This is not a failure, please do not report this as an issue.");
|
||||
DEBUG_INFO("To fix this, install and run the Looking Glass host as a service.");
|
||||
DEBUG_INFO("looking-glass-host.exe InstallService");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IDXGIDevice * dxgi;
|
||||
status = ID3D11Device_QueryInterface(this->device, &IID_IDXGIDevice, (void **)&dxgi);
|
||||
if (FAILED(status))
|
||||
@@ -674,6 +707,15 @@ static CaptureResult dxgi_capture()
|
||||
DXGI_OUTDUPL_FRAME_INFO frameInfo;
|
||||
IDXGIResource * res;
|
||||
|
||||
bool copyFrame = false;
|
||||
bool copyPointer = false;
|
||||
ID3D11Texture2D * src;
|
||||
|
||||
bool postPointer = false;
|
||||
CapturePointer pointer = { 0 };
|
||||
void * pointerShape = NULL;
|
||||
UINT pointerShapeSize = 0;
|
||||
|
||||
// release the prior frame
|
||||
result = dxgi_releaseFrame();
|
||||
if (result != CAPTURE_RESULT_OK)
|
||||
@@ -705,7 +747,7 @@ static CaptureResult dxgi_capture()
|
||||
// check if the texture is free, if not skip the frame to keep up
|
||||
if (tex->state == TEXTURE_STATE_UNUSED)
|
||||
{
|
||||
ID3D11Texture2D * src;
|
||||
copyFrame = true;
|
||||
status = IDXGIResource_QueryInterface(res, &IID_ID3D11Texture2D, (void **)&src);
|
||||
if (FAILED(status))
|
||||
{
|
||||
@@ -713,19 +755,51 @@ static CaptureResult dxgi_capture()
|
||||
IDXGIResource_Release(res);
|
||||
return CAPTURE_RESULT_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LOCKED({
|
||||
// issue the copy from GPU to CPU RAM and release the src
|
||||
IDXGIResource_Release(res);
|
||||
|
||||
// if the pointer shape has changed
|
||||
uint32_t bufferSize;
|
||||
if (frameInfo.PointerShapeBufferSize > 0)
|
||||
{
|
||||
if(!this->getPointerBufferFn(&pointerShape, &bufferSize))
|
||||
DEBUG_WARN("Failed to obtain a buffer for the pointer shape");
|
||||
else
|
||||
copyPointer = true;
|
||||
}
|
||||
|
||||
if (copyFrame || copyPointer)
|
||||
{
|
||||
DXGI_OUTDUPL_POINTER_SHAPE_INFO shapeInfo;
|
||||
LOCKED(
|
||||
{
|
||||
if (copyFrame)
|
||||
{
|
||||
// issue the copy from GPU to CPU RAM
|
||||
ID3D11DeviceContext_CopyResource(this->deviceContext,
|
||||
(ID3D11Resource *)tex->tex, (ID3D11Resource *)src);
|
||||
});
|
||||
}
|
||||
|
||||
if (copyPointer)
|
||||
{
|
||||
// grab the pointer shape
|
||||
status = IDXGIOutputDuplication_GetFramePointerShape(
|
||||
this->dup, bufferSize, pointerShape, &pointerShapeSize, &shapeInfo);
|
||||
}
|
||||
|
||||
ID3D11DeviceContext_Flush(this->deviceContext);
|
||||
});
|
||||
|
||||
if (copyFrame)
|
||||
{
|
||||
ID3D11Texture2D_Release(src);
|
||||
|
||||
// set the state, and signal
|
||||
tex->state = TEXTURE_STATE_PENDING_MAP;
|
||||
INTERLOCKED_INC(&this->texReady);
|
||||
lgSignalEvent(this->frameEvent);
|
||||
if (atomic_fetch_add_explicit(&this->texReady, 1, memory_order_relaxed) == 0)
|
||||
lgSignalEvent(this->frameEvent);
|
||||
|
||||
// advance the write index
|
||||
if (++this->texWIndex == this->maxTextures)
|
||||
@@ -734,16 +808,63 @@ static CaptureResult dxgi_capture()
|
||||
// update the last frame time
|
||||
this->frameTime.QuadPart = frameInfo.LastPresentTime.QuadPart;
|
||||
}
|
||||
|
||||
if (copyPointer)
|
||||
{
|
||||
result = dxgi_hResultToCaptureResult(status);
|
||||
if (result != CAPTURE_RESULT_OK)
|
||||
{
|
||||
if (result == CAPTURE_RESULT_ERROR)
|
||||
DEBUG_WINERROR("Failed to get the new pointer shape", status);
|
||||
return result;
|
||||
}
|
||||
|
||||
switch(shapeInfo.Type)
|
||||
{
|
||||
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR : pointer.format = CAPTURE_FMT_COLOR ; break;
|
||||
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: pointer.format = CAPTURE_FMT_MASKED; break;
|
||||
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME : pointer.format = CAPTURE_FMT_MONO ; break;
|
||||
default:
|
||||
DEBUG_ERROR("Unsupported cursor format");
|
||||
return CAPTURE_RESULT_ERROR;
|
||||
}
|
||||
|
||||
CURSORINFO ci = { .cbSize = sizeof(CURSORINFO) };
|
||||
if (!GetCursorInfo(&ci))
|
||||
{
|
||||
DEBUG_WINERROR("GetCursorInfo failed", GetLastError());
|
||||
return CAPTURE_RESULT_ERROR;
|
||||
}
|
||||
|
||||
if (ci.hCursor)
|
||||
{
|
||||
ICONINFO ii;
|
||||
if (!GetIconInfo(ci.hCursor, &ii))
|
||||
{
|
||||
DEBUG_WINERROR("GetIconInfo failed", GetLastError());
|
||||
return CAPTURE_RESULT_ERROR;
|
||||
}
|
||||
|
||||
DeleteObject(ii.hbmMask);
|
||||
DeleteObject(ii.hbmColor);
|
||||
|
||||
pointer.hx = ii.xHotspot;
|
||||
pointer.hy = ii.yHotspot;
|
||||
}
|
||||
else
|
||||
{
|
||||
pointer.hx = 0;
|
||||
pointer.hy = 0;
|
||||
}
|
||||
|
||||
pointer.shapeUpdate = true;
|
||||
pointer.width = shapeInfo.Width;
|
||||
pointer.height = shapeInfo.Height;
|
||||
pointer.pitch = shapeInfo.Pitch;
|
||||
postPointer = true;
|
||||
}
|
||||
}
|
||||
|
||||
IDXGIResource_Release(res);
|
||||
|
||||
// if the pointer has moved or changed state
|
||||
bool postPointer = false;
|
||||
CapturePointer pointer = { 0 };
|
||||
void * pointerShape = NULL;
|
||||
UINT pointerShapeSize = 0;
|
||||
|
||||
if (frameInfo.LastMouseUpdateTime.QuadPart)
|
||||
{
|
||||
/* the pointer position is only valid if the pointer is visible */
|
||||
@@ -768,43 +889,6 @@ static CaptureResult dxgi_capture()
|
||||
}
|
||||
}
|
||||
|
||||
// if the pointer shape has changed
|
||||
if (frameInfo.PointerShapeBufferSize > 0)
|
||||
{
|
||||
uint32_t bufferSize;
|
||||
if(!this->getPointerBufferFn(&pointerShape, &bufferSize))
|
||||
DEBUG_WARN("Failed to obtain a buffer for the pointer shape");
|
||||
else
|
||||
{
|
||||
DXGI_OUTDUPL_POINTER_SHAPE_INFO shapeInfo;
|
||||
|
||||
LOCKED({status = IDXGIOutputDuplication_GetFramePointerShape(this->dup, bufferSize, pointerShape, &pointerShapeSize, &shapeInfo);});
|
||||
result = dxgi_hResultToCaptureResult(status);
|
||||
if (result != CAPTURE_RESULT_OK)
|
||||
{
|
||||
if (result == CAPTURE_RESULT_ERROR)
|
||||
DEBUG_WINERROR("Failed to get the new pointer shape", status);
|
||||
return result;
|
||||
}
|
||||
|
||||
switch(shapeInfo.Type)
|
||||
{
|
||||
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR : pointer.format = CAPTURE_FMT_COLOR ; break;
|
||||
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: pointer.format = CAPTURE_FMT_MASKED; break;
|
||||
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME : pointer.format = CAPTURE_FMT_MONO ; break;
|
||||
default:
|
||||
DEBUG_ERROR("Unsupported cursor format");
|
||||
return CAPTURE_RESULT_ERROR;
|
||||
}
|
||||
|
||||
pointer.shapeUpdate = true;
|
||||
pointer.width = shapeInfo.Width;
|
||||
pointer.height = shapeInfo.Height;
|
||||
pointer.pitch = shapeInfo.Pitch;
|
||||
postPointer = true;
|
||||
}
|
||||
}
|
||||
|
||||
// post back the pointer information
|
||||
if (postPointer)
|
||||
{
|
||||
@@ -821,27 +905,39 @@ static CaptureResult dxgi_waitFrame(CaptureFrame * frame)
|
||||
assert(this->initialized);
|
||||
|
||||
// NOTE: the event may be signaled when there are no frames available
|
||||
if(this->texReady == 0)
|
||||
if(atomic_load_explicit(&this->texReady, memory_order_acquire) == 0)
|
||||
{
|
||||
if (!lgWaitEvent(this->frameEvent, 1000))
|
||||
return CAPTURE_RESULT_TIMEOUT;
|
||||
|
||||
if (this->texReady == 0)
|
||||
// the count will still be zero if we are stopping
|
||||
if(atomic_load_explicit(&this->texReady, memory_order_acquire) == 0)
|
||||
return CAPTURE_RESULT_TIMEOUT;
|
||||
}
|
||||
|
||||
Texture * tex = &this->texture[this->texRIndex];
|
||||
|
||||
// try to map the resource, but don't wait for it
|
||||
HRESULT status;
|
||||
LOCKED({status = ID3D11DeviceContext_Map(this->deviceContext, (ID3D11Resource*)tex->tex, 0, D3D11_MAP_READ, 0x100000L, &tex->map);});
|
||||
if (status == DXGI_ERROR_WAS_STILL_DRAWING)
|
||||
return CAPTURE_RESULT_TIMEOUT;
|
||||
|
||||
if (FAILED(status))
|
||||
for (int i = 0; ; ++i)
|
||||
{
|
||||
DEBUG_WINERROR("Failed to map the texture", status);
|
||||
return CAPTURE_RESULT_ERROR;
|
||||
HRESULT status;
|
||||
LOCKED({status = ID3D11DeviceContext_Map(this->deviceContext, (ID3D11Resource*)tex->tex, 0, D3D11_MAP_READ, 0x100000L, &tex->map);});
|
||||
if (status == DXGI_ERROR_WAS_STILL_DRAWING)
|
||||
{
|
||||
if (i == 100)
|
||||
return CAPTURE_RESULT_TIMEOUT;
|
||||
|
||||
usleep(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (FAILED(status))
|
||||
{
|
||||
DEBUG_WINERROR("Failed to map the texture", status);
|
||||
return CAPTURE_RESULT_ERROR;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
tex->state = TEXTURE_STATE_MAPPED;
|
||||
@@ -852,7 +948,7 @@ static CaptureResult dxgi_waitFrame(CaptureFrame * frame)
|
||||
frame->stride = this->stride;
|
||||
frame->format = this->format;
|
||||
|
||||
INTERLOCKED_DEC(&this->texReady);
|
||||
atomic_fetch_sub_explicit(&this->texReady, 1, memory_order_release);
|
||||
return CAPTURE_RESULT_OK;
|
||||
}
|
||||
|
||||
@@ -352,8 +352,8 @@ static int pointerThread(void * unused)
|
||||
}
|
||||
|
||||
this->mouseVisible = pointer.visible;
|
||||
this->mouseHotX = pointer.x;
|
||||
this->mouseHotY = pointer.y;
|
||||
this->mouseHotX = pointer.hx;
|
||||
this->mouseHotY = pointer.hy;
|
||||
}
|
||||
|
||||
if (events[0])
|
||||
@@ -288,8 +288,8 @@ CaptureResult NvFBCToSysGetCursor(NvFBCHandle handle, CapturePointer * pointer,
|
||||
return CAPTURE_RESULT_ERROR;
|
||||
}
|
||||
|
||||
pointer->x = params.dwXHotSpot;
|
||||
pointer->y = params.dwYHotSpot;
|
||||
pointer->hx = params.dwXHotSpot;
|
||||
pointer->hy = params.dwYHotSpot;
|
||||
pointer->width = params.dwWidth;
|
||||
pointer->height = params.dwHeight;
|
||||
pointer->pitch = params.dwPitch;
|
||||
@@ -327,4 +327,4 @@ CaptureResult NvFBCToSysGetCursor(NvFBCHandle handle, CapturePointer * pointer,
|
||||
|
||||
memcpy(buffer, params.pBits, params.dwBufferSize);
|
||||
return CAPTURE_RESULT_OK;
|
||||
}
|
||||
}
|
||||
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 "service.h"
|
||||
#include "windows/mousehook.h"
|
||||
|
||||
#include <windows.h>
|
||||
@@ -31,7 +32,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include "common/locking.h"
|
||||
#include "common/thread.h"
|
||||
|
||||
#define ID_MENU_OPEN_LOG 3000
|
||||
#define ID_MENU_SHOW_LOG 3000
|
||||
#define ID_MENU_EXIT 3001
|
||||
|
||||
struct AppState
|
||||
@@ -42,9 +43,11 @@ struct AppState
|
||||
int argc;
|
||||
char ** argv;
|
||||
|
||||
char executable[MAX_PATH + 1];
|
||||
HWND messageWnd;
|
||||
HMENU trayMenu;
|
||||
char executable[MAX_PATH + 1];
|
||||
HWND messageWnd;
|
||||
NOTIFYICONDATA iconData;
|
||||
UINT trayRestartMsg;
|
||||
HMENU trayMenu;
|
||||
};
|
||||
|
||||
static struct AppState app = {0};
|
||||
@@ -54,6 +57,30 @@ HWND MessageHWND;
|
||||
typedef NTSTATUS (__stdcall *ZwSetTimerResolution_t)(ULONG RequestedResolution, BOOLEAN Set, PULONG ActualResolution);
|
||||
static ZwSetTimerResolution_t ZwSetTimerResolution = NULL;
|
||||
|
||||
// linux mingw64 is missing this
|
||||
#ifndef MSGFLT_RESET
|
||||
#define MSGFLT_RESET (0)
|
||||
#define MSGFLT_ALLOW (1)
|
||||
#define MSGFLT_DISALLOW (2)
|
||||
#endif
|
||||
typedef WINBOOL WINAPI (*PChangeWindowMessageFilterEx)(HWND hwnd, UINT message, DWORD action, void * pChangeFilterStruct);
|
||||
PChangeWindowMessageFilterEx _ChangeWindowMessageFilterEx = NULL;
|
||||
|
||||
static void RegisterTrayIcon()
|
||||
{
|
||||
// register our TrayIcon
|
||||
if (!app.iconData.cbSize)
|
||||
{
|
||||
app.iconData.cbSize = sizeof(NOTIFYICONDATA);
|
||||
app.iconData.hWnd = app.messageWnd;
|
||||
app.iconData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
|
||||
app.iconData.uCallbackMessage = WM_TRAYICON;
|
||||
strncpy(app.iconData.szTip, "Looking Glass (host)", sizeof(app.iconData.szTip));
|
||||
app.iconData.hIcon = LoadIcon(app.hInst, IDI_APPLICATION);
|
||||
}
|
||||
Shell_NotifyIcon(NIM_ADD, &app.iconData);
|
||||
}
|
||||
|
||||
LRESULT CALLBACK DummyWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
switch(msg)
|
||||
@@ -86,41 +113,39 @@ LRESULT CALLBACK DummyWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
||||
);
|
||||
|
||||
if (clicked == ID_MENU_EXIT ) app_quit();
|
||||
else if (clicked == ID_MENU_OPEN_LOG)
|
||||
else if (clicked == ID_MENU_SHOW_LOG)
|
||||
{
|
||||
const char * logFile = option_get_string("os", "logFile");
|
||||
if (strcmp(logFile, "stderr") == 0)
|
||||
DEBUG_INFO("Ignoring request to open the logFile, logging to stderr");
|
||||
else
|
||||
ShellExecute(NULL, NULL, logFile, NULL, NULL, SW_SHOWNORMAL);
|
||||
{
|
||||
/* If LG is running as SYSTEM, ShellExecute would launch a process
|
||||
* as the SYSTEM user also, for security we will just show the file
|
||||
* location instead */
|
||||
//ShellExecute(NULL, NULL, logFile, NULL, NULL, SW_SHOWNORMAL);
|
||||
MessageBoxA(hwnd, logFile, "Log File Location", MB_OK | MB_ICONINFORMATION);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return DefWindowProc(hwnd, msg, wParam, lParam);
|
||||
if (msg == app.trayRestartMsg)
|
||||
RegisterTrayIcon();
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
|
||||
return DefWindowProc(hwnd, msg, wParam, lParam);
|
||||
}
|
||||
|
||||
static int appThread(void * opaque)
|
||||
{
|
||||
// register our TrayIcon
|
||||
NOTIFYICONDATA iconData =
|
||||
{
|
||||
.cbSize = sizeof(NOTIFYICONDATA),
|
||||
.hWnd = app.messageWnd,
|
||||
.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP,
|
||||
.uCallbackMessage = WM_TRAYICON,
|
||||
.szTip = "Looking Glass (host)"
|
||||
};
|
||||
iconData.hIcon = LoadIcon(app.hInst, IDI_APPLICATION);
|
||||
Shell_NotifyIcon(NIM_ADD, &iconData);
|
||||
|
||||
RegisterTrayIcon();
|
||||
int result = app_main(app.argc, app.argv);
|
||||
|
||||
Shell_NotifyIcon(NIM_DELETE, &iconData);
|
||||
Shell_NotifyIcon(NIM_DELETE, &app.iconData);
|
||||
mouseHook_remove();
|
||||
SendMessage(app.messageWnd, WM_DESTROY, 0, 0);
|
||||
return result;
|
||||
@@ -144,6 +169,21 @@ static BOOL WINAPI CtrlHandler(DWORD dwCtrlType)
|
||||
|
||||
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
|
||||
{
|
||||
// convert the command line to the standard argc and argv
|
||||
LPWSTR * wargv = CommandLineToArgvW(GetCommandLineW(), &app.argc);
|
||||
app.argv = malloc(sizeof(char *) * app.argc);
|
||||
for(int i = 0; i < app.argc; ++i)
|
||||
{
|
||||
const size_t s = (wcslen(wargv[i])+1) * 2;
|
||||
app.argv[i] = malloc(s);
|
||||
wcstombs(app.argv[i], wargv[i], s);
|
||||
}
|
||||
LocalFree(wargv);
|
||||
|
||||
GetModuleFileName(NULL, app.executable, sizeof(app.executable));
|
||||
if (HandleService(app.argc, app.argv))
|
||||
return 0;
|
||||
|
||||
/* this is a bit of a hack but without this --help will produce no output in a windows command prompt */
|
||||
if (!IsDebuggerPresent() && AttachConsole(ATTACH_PARENT_PROCESS))
|
||||
{
|
||||
@@ -183,19 +223,6 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
||||
option_register(options);
|
||||
free(logFilePath);
|
||||
|
||||
// convert the command line to the standard argc and argv
|
||||
LPWSTR * wargv = CommandLineToArgvW(GetCommandLineW(), &app.argc);
|
||||
app.argv = malloc(sizeof(char *) * app.argc);
|
||||
for(int i = 0; i < app.argc; ++i)
|
||||
{
|
||||
const size_t s = (wcslen(wargv[i])+1) * 2;
|
||||
app.argv[i] = malloc(s);
|
||||
wcstombs(app.argv[i], wargv[i], s);
|
||||
}
|
||||
LocalFree(wargv);
|
||||
|
||||
GetModuleFileName(NULL, app.executable, sizeof(app.executable));
|
||||
|
||||
// setup a handler for ctrl+c
|
||||
SetConsoleCtrlHandler(CtrlHandler, TRUE);
|
||||
|
||||
@@ -209,21 +236,31 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
||||
wx.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
|
||||
wx.hCursor = LoadCursor(NULL, IDC_ARROW);
|
||||
wx.hbrBackground = (HBRUSH)COLOR_APPWORKSPACE;
|
||||
if (!RegisterClassEx(&wx))
|
||||
ATOM class;
|
||||
if (!(class = RegisterClassEx(&wx)))
|
||||
{
|
||||
DEBUG_ERROR("Failed to register message window class");
|
||||
result = -1;
|
||||
goto finish;
|
||||
}
|
||||
app.messageWnd = CreateWindowEx(0, "DUMMY_CLASS", "DUMMY_NAME", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
|
||||
|
||||
app.trayRestartMsg = RegisterWindowMessage("TaskbarCreated");
|
||||
|
||||
app.messageWnd = CreateWindowEx(0, MAKEINTATOM(class), NULL, 0, 0, 0, 0, 0, NULL, NULL, hInstance, NULL);
|
||||
|
||||
// this is needed so that unprivileged processes can send us this message
|
||||
HMODULE user32 = GetModuleHandle("user32.dll");
|
||||
_ChangeWindowMessageFilterEx = (PChangeWindowMessageFilterEx)GetProcAddress(user32, "ChangeWindowMessageFilterEx");
|
||||
if (_ChangeWindowMessageFilterEx)
|
||||
_ChangeWindowMessageFilterEx(app.messageWnd, app.trayRestartMsg, MSGFLT_ALLOW, NULL);
|
||||
|
||||
// set the global
|
||||
MessageHWND = app.messageWnd;
|
||||
|
||||
app.trayMenu = CreatePopupMenu();
|
||||
AppendMenu(app.trayMenu, MF_STRING , ID_MENU_OPEN_LOG, "Open Log File");
|
||||
AppendMenu(app.trayMenu, MF_SEPARATOR, 0 , NULL );
|
||||
AppendMenu(app.trayMenu, MF_STRING , ID_MENU_EXIT , "Exit" );
|
||||
AppendMenu(app.trayMenu, MF_STRING , ID_MENU_SHOW_LOG, "Log File Location");
|
||||
AppendMenu(app.trayMenu, MF_SEPARATOR, 0 , NULL );
|
||||
AppendMenu(app.trayMenu, MF_STRING , ID_MENU_EXIT , "Exit" );
|
||||
|
||||
// create the application thread
|
||||
LGThread * thread;
|
||||
@@ -303,6 +340,24 @@ const char * os_getExecutable()
|
||||
return app.executable;
|
||||
}
|
||||
|
||||
const char * os_getDataPath()
|
||||
{
|
||||
static char path[MAX_PATH] = { 0 };
|
||||
if (!path[0])
|
||||
{
|
||||
if (!GetModuleFileName(NULL, path, MAX_PATH))
|
||||
return NULL;
|
||||
|
||||
char *p = strrchr(path, '\\');
|
||||
if (!p)
|
||||
return NULL;
|
||||
|
||||
++p;
|
||||
*p = '\0';
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
HWND os_getMessageWnd()
|
||||
{
|
||||
return app.messageWnd;
|
||||
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 <string.h>
|
||||
|
||||
#define CONFIG_FILE "looking-glass-host.ini"
|
||||
|
||||
#define ALIGN_DN(x) ((uintptr_t)(x) & ~0x7F)
|
||||
#define ALIGN_UP(x) ALIGN_DN(x + 0x7F)
|
||||
|
||||
@@ -60,12 +62,22 @@ static const struct LGMPQueueConfig POINTER_QUEUE_CONFIG =
|
||||
|
||||
#define MAX_POINTER_SIZE (sizeof(KVMFRCursor) + (128 * 128 * 4))
|
||||
|
||||
enum AppState
|
||||
{
|
||||
APP_STATE_RUNNING,
|
||||
APP_STATE_IDLE,
|
||||
APP_STATE_RESTART,
|
||||
APP_STATE_SHUTDOWN
|
||||
};
|
||||
|
||||
struct app
|
||||
{
|
||||
PLGMPHost lgmp;
|
||||
|
||||
PLGMPHostQueue pointerQueue;
|
||||
PLGMPMemory pointerMemory[LGMP_Q_POINTER_LEN];
|
||||
LG_Lock pointerLock;
|
||||
CapturePointer pointerInfo;
|
||||
PLGMPMemory pointerShape;
|
||||
bool pointerShapeValid;
|
||||
unsigned int pointerIndex;
|
||||
@@ -77,8 +89,7 @@ struct app
|
||||
|
||||
CaptureInterface * iface;
|
||||
|
||||
bool running;
|
||||
bool reinit;
|
||||
enum AppState state;
|
||||
LGTimer * lgmpTimer;
|
||||
LGThread * frameThread;
|
||||
};
|
||||
@@ -91,7 +102,7 @@ static bool lgmpTimer(void * opaque)
|
||||
if ((status = lgmpHostProcess(app.lgmp)) != LGMP_OK)
|
||||
{
|
||||
DEBUG_ERROR("lgmpHostProcess Failed: %s", lgmpStatusString(status));
|
||||
app.running = false;
|
||||
app.state = APP_STATE_SHUTDOWN;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -107,7 +118,7 @@ static int frameThread(void * opaque)
|
||||
CaptureFrame frame = { 0 };
|
||||
const long pageSize = sysinfo_getPageSize();
|
||||
|
||||
while(app.running)
|
||||
while(app.state == APP_STATE_RUNNING)
|
||||
{
|
||||
//wait until there is room in the queue
|
||||
if(lgmpHostQueuePending(app.frameQueue) == LGMP_Q_FRAME_LEN)
|
||||
@@ -124,7 +135,7 @@ static int frameThread(void * opaque)
|
||||
|
||||
case CAPTURE_RESULT_REINIT:
|
||||
{
|
||||
app.reinit = true;
|
||||
app.state = APP_STATE_RESTART;
|
||||
DEBUG_INFO("Frame thread reinit");
|
||||
return 0;
|
||||
}
|
||||
@@ -201,13 +212,7 @@ static int frameThread(void * opaque)
|
||||
|
||||
bool startThreads()
|
||||
{
|
||||
app.running = true;
|
||||
if (!lgCreateTimer(100, lgmpTimer, NULL, &app.lgmpTimer))
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the LGMP timer");
|
||||
return false;
|
||||
}
|
||||
|
||||
app.state = APP_STATE_RUNNING;
|
||||
if (!lgCreateThread("FrameThread", frameThread, NULL, &app.frameThread))
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the frame thread");
|
||||
@@ -221,8 +226,9 @@ bool stopThreads()
|
||||
{
|
||||
bool ok = true;
|
||||
|
||||
app.running = false;
|
||||
app.iface->stop();
|
||||
if (app.state != APP_STATE_SHUTDOWN)
|
||||
app.state = APP_STATE_IDLE;
|
||||
|
||||
if (app.frameThread && !lgJoinThread(app.frameThread, NULL))
|
||||
{
|
||||
@@ -231,18 +237,19 @@ bool stopThreads()
|
||||
}
|
||||
app.frameThread = NULL;
|
||||
|
||||
if (app.lgmpTimer)
|
||||
{
|
||||
lgTimerDestroy(app.lgmpTimer);
|
||||
app.lgmpTimer = NULL;
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
static bool captureStart()
|
||||
{
|
||||
DEBUG_INFO("Using : %s", app.iface->getName());
|
||||
if (app.state == APP_STATE_IDLE)
|
||||
{
|
||||
if (!app.iface->init())
|
||||
{
|
||||
DEBUG_ERROR("Initialize the capture device");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const unsigned int maxFrameSize = app.iface->getMaxFrameSize();
|
||||
if (maxFrameSize > app.maxFrameSize)
|
||||
@@ -256,31 +263,33 @@ static bool captureStart()
|
||||
return startThreads();
|
||||
}
|
||||
|
||||
static bool captureRestart()
|
||||
static bool captureStop()
|
||||
{
|
||||
DEBUG_INFO("==== [ Capture Restart ] ====");
|
||||
DEBUG_INFO("==== [ Capture Stop ] ====");
|
||||
if (!stopThreads())
|
||||
return false;
|
||||
|
||||
if (!app.iface->deinit() || !app.iface->init())
|
||||
if (!app.iface->deinit())
|
||||
{
|
||||
DEBUG_ERROR("Failed to reinitialize the capture device");
|
||||
DEBUG_ERROR("Failed to deinitialize the capture device");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!captureStart())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool captureRestart()
|
||||
{
|
||||
return captureStop() && captureStart();
|
||||
}
|
||||
|
||||
bool captureGetPointerBuffer(void ** data, uint32_t * size)
|
||||
{
|
||||
// spin until there is room
|
||||
while(lgmpHostQueuePending(app.pointerQueue) == LGMP_Q_POINTER_LEN)
|
||||
{
|
||||
DEBUG_INFO("pending");
|
||||
if (!app.running)
|
||||
usleep(1);
|
||||
if (app.state == APP_STATE_RUNNING)
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -290,14 +299,12 @@ bool captureGetPointerBuffer(void ** data, uint32_t * size)
|
||||
return true;
|
||||
}
|
||||
|
||||
void capturePostPointerBuffer(CapturePointer pointer)
|
||||
static void sendPointer(bool newClient)
|
||||
{
|
||||
PLGMPMemory mem;
|
||||
const bool newClient = lgmpHostQueueNewSubs(app.pointerQueue) > 0;
|
||||
|
||||
if (pointer.shapeUpdate || newClient)
|
||||
if (app.pointerInfo.shapeUpdate || newClient)
|
||||
{
|
||||
if (pointer.shapeUpdate)
|
||||
if (!newClient)
|
||||
{
|
||||
// swap the latest shape buffer out of rotation
|
||||
PLGMPMemory tmp = app.pointerShape;
|
||||
@@ -309,32 +316,32 @@ void capturePostPointerBuffer(CapturePointer pointer)
|
||||
mem = app.pointerShape;
|
||||
}
|
||||
else
|
||||
{
|
||||
mem = app.pointerMemory[app.pointerIndex];
|
||||
if (++app.pointerIndex == LGMP_Q_POINTER_LEN)
|
||||
app.pointerIndex = 0;
|
||||
}
|
||||
|
||||
if (++app.pointerIndex == LGMP_Q_POINTER_LEN)
|
||||
app.pointerIndex = 0;
|
||||
|
||||
uint32_t flags = 0;
|
||||
KVMFRCursor *cursor = lgmpHostMemPtr(mem);
|
||||
|
||||
if (pointer.positionUpdate)
|
||||
if (app.pointerInfo.positionUpdate || newClient)
|
||||
{
|
||||
flags |= CURSOR_FLAG_POSITION;
|
||||
cursor->x = pointer.x;
|
||||
cursor->y = pointer.y;
|
||||
cursor->x = app.pointerInfo.x;
|
||||
cursor->y = app.pointerInfo.y;
|
||||
}
|
||||
|
||||
if (pointer.visible)
|
||||
if (app.pointerInfo.visible)
|
||||
flags |= CURSOR_FLAG_VISIBLE;
|
||||
|
||||
if (pointer.shapeUpdate)
|
||||
if (app.pointerInfo.shapeUpdate)
|
||||
{
|
||||
// remember which slot has the latest shape
|
||||
cursor->width = pointer.width;
|
||||
cursor->height = pointer.height;
|
||||
cursor->pitch = pointer.pitch;
|
||||
switch(pointer.format)
|
||||
cursor->hx = app.pointerInfo.hx;
|
||||
cursor->hy = app.pointerInfo.hy;
|
||||
cursor->width = app.pointerInfo.width;
|
||||
cursor->height = app.pointerInfo.height;
|
||||
cursor->pitch = app.pointerInfo.pitch;
|
||||
switch(app.pointerInfo.format)
|
||||
{
|
||||
case CAPTURE_FMT_COLOR : cursor->type = CURSOR_TYPE_COLOR ; break;
|
||||
case CAPTURE_FMT_MONO : cursor->type = CURSOR_TYPE_MONOCHROME ; break;
|
||||
@@ -348,7 +355,7 @@ void capturePostPointerBuffer(CapturePointer pointer)
|
||||
app.pointerShapeValid = true;
|
||||
}
|
||||
|
||||
if ((pointer.shapeUpdate || newClient) && app.pointerShapeValid)
|
||||
if ((app.pointerInfo.shapeUpdate || newClient) && app.pointerShapeValid)
|
||||
flags |= CURSOR_FLAG_SHAPE;
|
||||
|
||||
LGMP_STATUS status;
|
||||
@@ -361,10 +368,31 @@ void capturePostPointerBuffer(CapturePointer pointer)
|
||||
}
|
||||
|
||||
DEBUG_ERROR("lgmpHostQueuePost Failed (Pointer): %s", lgmpStatusString(status));
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void capturePostPointerBuffer(CapturePointer pointer)
|
||||
{
|
||||
LG_LOCK(app.pointerLock);
|
||||
|
||||
int x = app.pointerInfo.x;
|
||||
int y = app.pointerInfo.y;
|
||||
|
||||
memcpy(&app.pointerInfo, &pointer, sizeof(CapturePointer));
|
||||
|
||||
/* if there was not a position update, restore the x & y */
|
||||
if (!pointer.positionUpdate)
|
||||
{
|
||||
app.pointerInfo.x = x;
|
||||
app.pointerInfo.y = y;
|
||||
}
|
||||
|
||||
sendPointer(false);
|
||||
|
||||
LG_UNLOCK(app.pointerLock);
|
||||
}
|
||||
|
||||
// this is called from the platform specific startup routine
|
||||
int app_main(int argc, char * argv[])
|
||||
{
|
||||
@@ -379,7 +407,22 @@ int app_main(int argc, char * argv[])
|
||||
CaptureInterfaces[i]->initOptions();
|
||||
|
||||
// try load values from a config file
|
||||
option_load("looking-glass-host.ini");
|
||||
const char * dataPath = os_getDataPath();
|
||||
if (!dataPath)
|
||||
{
|
||||
option_free();
|
||||
DEBUG_ERROR("Failed to get the application's data path");
|
||||
return -1;
|
||||
}
|
||||
|
||||
const size_t len = strlen(dataPath) + sizeof(CONFIG_FILE) + 1;
|
||||
char configFile[len];
|
||||
snprintf(configFile, sizeof(configFile), "%s%s", dataPath, CONFIG_FILE);
|
||||
DEBUG_INFO("Looking for configuration file at: %s", configFile);
|
||||
if (option_load(configFile))
|
||||
DEBUG_INFO("Configuration file loaded");
|
||||
else
|
||||
DEBUG_INFO("Configuration file not found or invalid");
|
||||
|
||||
// parse the command line arguments
|
||||
if (!option_parse(argc, argv))
|
||||
@@ -414,9 +457,10 @@ int app_main(int argc, char * argv[])
|
||||
DEBUG_INFO("Max Pointer Size : %u KiB", (unsigned int)MAX_POINTER_SIZE / 1024);
|
||||
DEBUG_INFO("KVMFR Version : %u", KVMFR_VERSION);
|
||||
|
||||
KVMFR udata = {
|
||||
const KVMFR udata = {
|
||||
.magic = KVMFR_MAGIC,
|
||||
.version = KVMFR_VERSION
|
||||
.version = KVMFR_VERSION,
|
||||
.hostver = BUILD_VERSION
|
||||
};
|
||||
|
||||
LGMP_STATUS status;
|
||||
@@ -446,6 +490,7 @@ int app_main(int argc, char * argv[])
|
||||
DEBUG_ERROR("lgmpHostMemAlloc Failed (Pointer): %s", lgmpStatusString(status));
|
||||
goto fail;
|
||||
}
|
||||
memset(lgmpHostMemPtr(app.pointerMemory[i]), 0, MAX_POINTER_SIZE);
|
||||
}
|
||||
|
||||
app.pointerShapeValid = false;
|
||||
@@ -496,50 +541,87 @@ int app_main(int argc, char * argv[])
|
||||
goto fail;
|
||||
}
|
||||
|
||||
DEBUG_INFO("Using : %s", iface->getName());
|
||||
|
||||
app.state = APP_STATE_RUNNING;
|
||||
app.iface = iface;
|
||||
|
||||
if (!captureStart())
|
||||
LG_LOCK_INIT(app.pointerLock);
|
||||
|
||||
if (!lgCreateTimer(100, lgmpTimer, NULL, &app.lgmpTimer))
|
||||
{
|
||||
exitcode = -1;
|
||||
goto exit;
|
||||
DEBUG_ERROR("Failed to create the LGMP timer");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
while(app.running)
|
||||
while(app.state != APP_STATE_SHUTDOWN)
|
||||
{
|
||||
if (app.reinit && !captureRestart())
|
||||
if(lgmpHostQueueHasSubs(app.pointerQueue) ||
|
||||
lgmpHostQueueHasSubs(app.frameQueue))
|
||||
{
|
||||
exitcode = -1;
|
||||
goto exit;
|
||||
if (!captureStart())
|
||||
{
|
||||
exitcode = -1;
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
app.reinit = false;
|
||||
|
||||
switch(iface->capture())
|
||||
else
|
||||
{
|
||||
case CAPTURE_RESULT_OK:
|
||||
break;
|
||||
usleep(100000);
|
||||
continue;
|
||||
}
|
||||
|
||||
case CAPTURE_RESULT_TIMEOUT:
|
||||
continue;
|
||||
|
||||
case CAPTURE_RESULT_REINIT:
|
||||
while(app.state != APP_STATE_SHUTDOWN && (
|
||||
lgmpHostQueueHasSubs(app.pointerQueue) ||
|
||||
lgmpHostQueueHasSubs(app.frameQueue)))
|
||||
{
|
||||
if (app.state == APP_STATE_RESTART)
|
||||
{
|
||||
if (!captureRestart())
|
||||
{
|
||||
exitcode = -1;
|
||||
goto exit;
|
||||
}
|
||||
app.reinit = false;
|
||||
continue;
|
||||
app.state = APP_STATE_RUNNING;
|
||||
}
|
||||
|
||||
case CAPTURE_RESULT_ERROR:
|
||||
DEBUG_ERROR("Capture interface reported a fatal error");
|
||||
exitcode = -1;
|
||||
goto finish;
|
||||
if (lgmpHostQueueNewSubs(app.pointerQueue) > 0)
|
||||
{
|
||||
LG_LOCK(app.pointerLock);
|
||||
sendPointer(true);
|
||||
LG_UNLOCK(app.pointerLock);
|
||||
}
|
||||
|
||||
switch(iface->capture())
|
||||
{
|
||||
case CAPTURE_RESULT_OK:
|
||||
break;
|
||||
|
||||
case CAPTURE_RESULT_TIMEOUT:
|
||||
continue;
|
||||
|
||||
case CAPTURE_RESULT_REINIT:
|
||||
app.state = APP_STATE_RESTART;
|
||||
continue;
|
||||
|
||||
case CAPTURE_RESULT_ERROR:
|
||||
DEBUG_ERROR("Capture interface reported a fatal error");
|
||||
exitcode = -1;
|
||||
goto finish;
|
||||
}
|
||||
}
|
||||
|
||||
if (app.state != APP_STATE_SHUTDOWN)
|
||||
DEBUG_INFO("No subscribers, going to sleep...");
|
||||
captureStop();
|
||||
}
|
||||
|
||||
finish:
|
||||
stopThreads();
|
||||
|
||||
exit:
|
||||
lgTimerDestroy(app.lgmpTimer);
|
||||
LG_LOCK_FREE(app.pointerLock);
|
||||
|
||||
iface->deinit();
|
||||
iface->free();
|
||||
@@ -558,5 +640,5 @@ fail:
|
||||
|
||||
void app_quit()
|
||||
{
|
||||
app.running = false;
|
||||
app.state = APP_STATE_SHUTDOWN;
|
||||
}
|
||||
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/util/threading.h>
|
||||
|
||||
@@ -193,6 +195,7 @@ static void lgUpdate(void * data, obs_data_t * settings)
|
||||
|
||||
this->state = STATE_STARTING;
|
||||
pthread_create(&this->frameThread, NULL, frameThread, this);
|
||||
pthread_setname_np(this->frameThread, "LGFrameThread");
|
||||
}
|
||||
|
||||
static void lgVideoTick(void * data, float seconds)
|
||||
@@ -212,6 +215,16 @@ static void lgVideoTick(void * data, float seconds)
|
||||
return;
|
||||
}
|
||||
|
||||
if ((status = lgmpClientAdvanceToLast(this->frameQueue)) != LGMP_OK)
|
||||
{
|
||||
if (status != LGMP_ERR_QUEUE_EMPTY)
|
||||
{
|
||||
os_sem_post(this->frameSem);
|
||||
printf("lgmpClientAdvanceToLast: %s\n", lgmpStatusString(status));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ((status = lgmpClientProcess(this->frameQueue, &msg)) != LGMP_OK)
|
||||
{
|
||||
if (status == LGMP_ERR_QUEUE_EMPTY)
|
||||
|
||||
Submodule repos/LGMP updated: 19efde39f6...2a1477550c
Reference in New Issue
Block a user