mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-01-16 16:52:28 +00:00
Compare commits
48 Commits
B2-rc3
...
Release/B2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76710ef201 | ||
|
|
e20c8a5cc7 | ||
|
|
4f4d2dbf42 | ||
|
|
8692e9af80 | ||
|
|
7d2b39058c | ||
|
|
6927dbecd2 | ||
|
|
f9b6dcc986 | ||
|
|
5c912e3c27 | ||
|
|
7e362050f7 | ||
|
|
10fbdeb294 | ||
|
|
72d70e8322 | ||
|
|
c66a339bbc | ||
|
|
1c7961daeb | ||
|
|
cdc3384883 | ||
|
|
969effedde | ||
|
|
dc4d1d49fa | ||
|
|
4e1f947a09 | ||
|
|
15d1a74291 | ||
|
|
7dba6b9b08 | ||
|
|
a5ad531004 | ||
|
|
c119b3dcca | ||
|
|
e2f2437ef4 | ||
|
|
b2980fea63 | ||
|
|
2b518690b8 | ||
|
|
92aca75792 | ||
|
|
64fdb8b7bb | ||
|
|
431ae3fc55 | ||
|
|
380b5df9f9 | ||
|
|
c7330167cf | ||
|
|
ca02e1aba9 | ||
|
|
ca4b1f5592 | ||
|
|
0cf1e27709 | ||
|
|
045932ce77 | ||
|
|
bf5481446b | ||
|
|
e3f97e384b | ||
|
|
76e119f8ad | ||
|
|
bfb12c74fb | ||
|
|
fa50b7824c | ||
|
|
da8b2d0cec | ||
|
|
74649ddb96 | ||
|
|
4619ddef5d | ||
|
|
ea74ee6e25 | ||
|
|
ecd73aa670 | ||
|
|
10d9678b3d | ||
|
|
e08d3afdbc | ||
|
|
9a6b598438 | ||
|
|
d9a80b16f0 | ||
|
|
90d0cd873d |
74
.github/issue_template.md
vendored
74
.github/issue_template.md
vendored
@@ -1,11 +1,69 @@
|
|||||||
### Required information
|
### Issues are for Bug Reports and Feature Requests Only!
|
||||||
|
|
||||||
Host CPU:
|
If you are looking for help or support please use one of the following methods
|
||||||
Host GPU:
|
|
||||||
Guest GPU:
|
|
||||||
Host Kernel version:
|
|
||||||
Host QEMU version:
|
|
||||||
|
|
||||||
Please describe what were you doing when the problem occured. If the Windows host application crashed please check for file named `looking-glass-host.dmp` and attach it to this bug report.
|
Create a New Topic on the Level1Tech's forum under the Looking Glass category:
|
||||||
|
* https://forum.level1techs.com/c/software/lookingGlass/142
|
||||||
|
|
||||||
**Reports that do not include this information will be ignored and closed**
|
Ask for help in #looking-glass in the VFIO discord server
|
||||||
|
* https://discord.gg/4ahCn4c
|
||||||
|
|
||||||
|
*Issues that are not bug reports or feature requests will be closed & ignored*
|
||||||
|
|
||||||
|
### Errors that are not bugs
|
||||||
|
|
||||||
|
Some errors generated by the LG client are not bugs, but rather issues with your
|
||||||
|
system's configuration and/or timing. Please do not report these, but rather use
|
||||||
|
one of the above resources to ask for advice/help.
|
||||||
|
|
||||||
|
* `LGMP_ERR_QUEUE_UNSUBSCRIBED` - Failure to heed advice on things such as
|
||||||
|
using `isolcpus` and CPU pinning may result in this message, especially if you
|
||||||
|
are over-taxing your CPU.
|
||||||
|
|
||||||
|
* `Could not create an SDL window: *` - Failure to create a SDL window is not an
|
||||||
|
issue with Looking Glass but rather a more substantial issue with your system,
|
||||||
|
such as missing hardware support for the RGBA32 pixmap format, or missing
|
||||||
|
required OpenGL EGL features.
|
||||||
|
|
||||||
|
* `The host application is not compatible with this client` - The Looking Glass
|
||||||
|
Host application in Windows is the incorrect version and is not compatible,
|
||||||
|
you need to make sure you run matching versions of both the host and client
|
||||||
|
applications.
|
||||||
|
|
||||||
|
### Bug Report Required Information
|
||||||
|
|
||||||
|
The entire (not truncated) output from the client application (if applicable).
|
||||||
|
To obtain this run `looking-glass-client` in a terminal.
|
||||||
|
|
||||||
|
```
|
||||||
|
PASTE CLIENT OUTPUT HERE
|
||||||
|
```
|
||||||
|
|
||||||
|
The entire (not truncated) log file from the host application (if applicable).
|
||||||
|
To obtain this locate the log file on your system, it will be in one of the
|
||||||
|
following two locations depending on how you are launching the Looking Glass Host
|
||||||
|
application:
|
||||||
|
|
||||||
|
* C:\Windows\Temp\looking-glass.txt
|
||||||
|
* C:\Users\YOUR_USER\AppData\Local\Temp\looking-glass.txt
|
||||||
|
|
||||||
|
This log may be quite long, please delete the file first and then proceed to
|
||||||
|
launch the host and reproduce the issue so that the log only contains the
|
||||||
|
pertinent information.
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
PASTE HOST LOG FILE CONTENTS HERE
|
||||||
|
```
|
||||||
|
|
||||||
|
If the client is unexpetedly exiting without a backtrace, please provide one via
|
||||||
|
gdb with the command `thread apply all bt`. If you are unsure how to do this
|
||||||
|
please watch the video below on how to perform a Debug build and generate this
|
||||||
|
backtrace.
|
||||||
|
|
||||||
|
https://www.youtube.com/watch?v=EqxxJK9Yo64
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
PASTE FULL BACKTRACE HERE
|
||||||
|
```
|
||||||
|
|||||||
13
README.md
13
README.md
@@ -4,6 +4,7 @@ An extremely low latency KVMFR (KVM FrameRelay) implementation for guests with
|
|||||||
VGA PCI Passthrough.
|
VGA PCI Passthrough.
|
||||||
|
|
||||||
* Project Website: https://looking-glass.hostfission.com
|
* Project Website: https://looking-glass.hostfission.com
|
||||||
|
* Getting Started: https://looking-glass.hostfission.com/wiki/Installation
|
||||||
|
|
||||||
## Donations
|
## Donations
|
||||||
|
|
||||||
@@ -23,18 +24,20 @@ support me directly using the following platforms.
|
|||||||
|
|
||||||
** IMPORTANT **
|
** IMPORTANT **
|
||||||
This project contains submodules that must be checked out if building from the
|
This project contains submodules that must be checked out if building from the
|
||||||
git repository!
|
git repository! If you are not a developer and just want to compile Looking
|
||||||
|
Glass please download the source archive from the website instead:
|
||||||
|
|
||||||
|
https://looking-glass.hostfission.com/downloads
|
||||||
|
|
||||||
Please also be sure to see the following files for more information
|
Please also be sure to see the following files for more information
|
||||||
|
Note: The `README.md` files are slowly being deprecated from this project in
|
||||||
|
favor of the wiki at https://looking-glass.hostfission.com/wiki, and as such the
|
||||||
|
information in these files may be dated.
|
||||||
|
|
||||||
* [client/README.md](client/README.md)
|
* [client/README.md](client/README.md)
|
||||||
* [host/README.md](host/README.md)
|
* [host/README.md](host/README.md)
|
||||||
* [module/README.md](module/README.md)
|
* [module/README.md](module/README.md)
|
||||||
|
|
||||||
## Obtaining and using Looking Glass
|
|
||||||
|
|
||||||
Please see https://looking-glass.hostfission.com/wiki/
|
|
||||||
|
|
||||||
## Latest Version
|
## Latest Version
|
||||||
|
|
||||||
If you would like to use the latest bleeding edge version of Looking Glass please
|
If you would like to use the latest bleeding edge version of Looking Glass please
|
||||||
|
|||||||
@@ -35,23 +35,6 @@ Should this all go well you should be left with the file `looking-glass-client`
|
|||||||
|
|
||||||
## Usage Tips
|
## Usage Tips
|
||||||
|
|
||||||
### High priority capture using DXGI and Secure Desktop (UAC) capture support
|
|
||||||
|
|
||||||
By default Windows gives priority to the foreground application for any GPU
|
|
||||||
work which causes issues with capture if the foreground application is consuming
|
|
||||||
100% of the available GPU resources. The looking glass host application is able
|
|
||||||
to increase the kernel GPU thread to realtime priority which fixes this, but in
|
|
||||||
order to do so it must run as the `SYSTEM` user account. To do this, please use
|
|
||||||
`PsExec` from SysInternals (Microsoft), for example:
|
|
||||||
|
|
||||||
PsExec64.exe -s -i -d looking-glass-host.exe
|
|
||||||
|
|
||||||
This will also enable the host application to capture the secure desktop which
|
|
||||||
includes things like the lock screen and UAC prompts.
|
|
||||||
|
|
||||||
A future update (likely Beta 3) will include a service launcher for the Looking
|
|
||||||
Glass host which will remove the need for `PsExec`.
|
|
||||||
|
|
||||||
### Key Bindings
|
### Key Bindings
|
||||||
|
|
||||||
By default Looking Glass uses the `Scroll Lock` key as the escape key for commands as well as the input capture mode toggle, this can be changed using the `-m` switch if you desire a different key.
|
By default Looking Glass uses the `Scroll Lock` key as the escape key for commands as well as the input capture mode toggle, this can be changed using the `-m` switch if you desire a different key.
|
||||||
@@ -159,6 +142,7 @@ Command line arguments will override any options loaded from the config files.
|
|||||||
| spice:clipboardToVM | | yes | Allow the clipboard to be syncronized TO the VM |
|
| spice:clipboardToVM | | yes | Allow the clipboard to be syncronized TO the VM |
|
||||||
| spice:clipboardToLocal | | yes | Allow the clipboard to be syncronized FROM the VM |
|
| spice:clipboardToLocal | | yes | Allow the clipboard to be syncronized FROM the VM |
|
||||||
| spice:scaleCursor | -j | yes | Scale cursor input position to screen size when up/down scaled |
|
| spice:scaleCursor | -j | yes | Scale cursor input position to screen size when up/down scaled |
|
||||||
|
| spice:captureOnStart | | no | Capture mouse and keyboard on start |
|
||||||
|------------------------------------------------------------------------------------------------------------------|
|
|------------------------------------------------------------------------------------------------------------------|
|
||||||
|
|
||||||
|--------------------------------------------------------------------------|
|
|--------------------------------------------------------------------------|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
|||||||
(x)->create && \
|
(x)->create && \
|
||||||
(x)->initialize && \
|
(x)->initialize && \
|
||||||
(x)->deinitialize && \
|
(x)->deinitialize && \
|
||||||
|
(x)->on_restart && \
|
||||||
(x)->on_resize && \
|
(x)->on_resize && \
|
||||||
(x)->on_mouse_shape && \
|
(x)->on_mouse_shape && \
|
||||||
(x)->on_mouse_event && \
|
(x)->on_mouse_event && \
|
||||||
@@ -87,6 +88,7 @@ typedef void (* LG_RendererSetup)();
|
|||||||
typedef bool (* LG_RendererCreate )(void ** opaque, const LG_RendererParams params);
|
typedef bool (* LG_RendererCreate )(void ** opaque, const LG_RendererParams params);
|
||||||
typedef bool (* LG_RendererInitialize )(void * opaque, Uint32 * sdlFlags);
|
typedef bool (* LG_RendererInitialize )(void * opaque, Uint32 * sdlFlags);
|
||||||
typedef void (* LG_RendererDeInitialize)(void * opaque);
|
typedef void (* LG_RendererDeInitialize)(void * opaque);
|
||||||
|
typedef void (* LG_RendererOnRestart )(void * opaque);
|
||||||
typedef void (* LG_RendererOnResize )(void * opaque, const int width, const int height, const LG_RendererRect destRect);
|
typedef void (* LG_RendererOnResize )(void * opaque, const int width, const int height, const LG_RendererRect destRect);
|
||||||
typedef bool (* LG_RendererOnMouseShape)(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data);
|
typedef bool (* LG_RendererOnMouseShape)(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data);
|
||||||
typedef bool (* LG_RendererOnMouseEvent)(void * opaque, const bool visible , const int x, const int y);
|
typedef bool (* LG_RendererOnMouseEvent)(void * opaque, const bool visible , const int x, const int y);
|
||||||
@@ -103,6 +105,7 @@ typedef struct LG_Renderer
|
|||||||
LG_RendererCreate create;
|
LG_RendererCreate create;
|
||||||
LG_RendererInitialize initialize;
|
LG_RendererInitialize initialize;
|
||||||
LG_RendererDeInitialize deinitialize;
|
LG_RendererDeInitialize deinitialize;
|
||||||
|
LG_RendererOnRestart on_restart;
|
||||||
LG_RendererOnResize on_resize;
|
LG_RendererOnResize on_resize;
|
||||||
LG_RendererOnMouseShape on_mouse_shape;
|
LG_RendererOnMouseShape on_mouse_shape;
|
||||||
LG_RendererOnMouseEvent on_mouse_event;
|
LG_RendererOnMouseEvent on_mouse_event;
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ struct Inst
|
|||||||
EGL_Alert * alert; // the alert display
|
EGL_Alert * alert; // the alert display
|
||||||
|
|
||||||
LG_RendererFormat format;
|
LG_RendererFormat format;
|
||||||
|
bool start;
|
||||||
uint64_t waitFadeTime;
|
uint64_t waitFadeTime;
|
||||||
bool waitDone;
|
bool waitDone;
|
||||||
|
|
||||||
@@ -225,6 +226,15 @@ void egl_deinitialize(void * opaque)
|
|||||||
free(this);
|
free(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void egl_on_restart(void * opaque)
|
||||||
|
{
|
||||||
|
struct Inst * this = (struct Inst *)opaque;
|
||||||
|
|
||||||
|
eglDestroyContext(this->display, this->frameContext);
|
||||||
|
this->frameContext = NULL;
|
||||||
|
this->start = false;
|
||||||
|
}
|
||||||
|
|
||||||
void egl_on_resize(void * opaque, const int width, const int height, const LG_RendererRect destRect)
|
void egl_on_resize(void * opaque, const int width, const int height, const LG_RendererRect destRect)
|
||||||
{
|
{
|
||||||
struct Inst * this = (struct Inst *)opaque;
|
struct Inst * this = (struct Inst *)opaque;
|
||||||
@@ -340,6 +350,7 @@ bool egl_on_frame_event(void * opaque, const LG_RendererFormat format, const Fra
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this->start = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -532,7 +543,10 @@ bool egl_render(void * opaque, SDL_Window * window)
|
|||||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
if (egl_desktop_render(this->desktop, this->translateX, this->translateY, this->scaleX, this->scaleY, this->useNearest))
|
if (this->start && egl_desktop_render(this->desktop,
|
||||||
|
this->translateX, this->translateY,
|
||||||
|
this->scaleX , this->scaleY ,
|
||||||
|
this->useNearest))
|
||||||
{
|
{
|
||||||
if (!this->waitFadeTime)
|
if (!this->waitFadeTime)
|
||||||
this->waitFadeTime = microtime() + SPLASH_FADE_TIME;
|
this->waitFadeTime = microtime() + SPLASH_FADE_TIME;
|
||||||
@@ -559,6 +573,11 @@ bool egl_render(void * opaque, SDL_Window * window)
|
|||||||
if (!this->waitDone)
|
if (!this->waitDone)
|
||||||
egl_splash_render(this->splash, a, this->splashRatio);
|
egl_splash_render(this->splash, a, this->splashRatio);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!this->start)
|
||||||
|
egl_splash_render(this->splash, 1.0f, this->splashRatio);
|
||||||
|
}
|
||||||
|
|
||||||
if (this->showAlert)
|
if (this->showAlert)
|
||||||
{
|
{
|
||||||
@@ -595,6 +614,7 @@ struct LG_Renderer LGR_EGL =
|
|||||||
.create = egl_create,
|
.create = egl_create,
|
||||||
.initialize = egl_initialize,
|
.initialize = egl_initialize,
|
||||||
.deinitialize = egl_deinitialize,
|
.deinitialize = egl_deinitialize,
|
||||||
|
.on_restart = egl_on_restart,
|
||||||
.on_resize = egl_on_resize,
|
.on_resize = egl_on_resize,
|
||||||
.on_mouse_shape = egl_on_mouse_shape,
|
.on_mouse_shape = egl_on_mouse_shape,
|
||||||
.on_mouse_event = egl_on_mouse_event,
|
.on_mouse_event = egl_on_mouse_event,
|
||||||
|
|||||||
@@ -319,7 +319,7 @@ bool egl_texture_update(EGL_Texture * texture, const uint8_t * buffer)
|
|||||||
const uint8_t sw =
|
const uint8_t sw =
|
||||||
atomic_load_explicit(&texture->state.w, memory_order_acquire);
|
atomic_load_explicit(&texture->state.w, memory_order_acquire);
|
||||||
|
|
||||||
if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == sw + 1)
|
if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == (uint8_t)(sw + 1))
|
||||||
{
|
{
|
||||||
egl_warn_slow();
|
egl_warn_slow();
|
||||||
return true;
|
return true;
|
||||||
@@ -355,7 +355,7 @@ bool egl_texture_update_from_frame(EGL_Texture * texture, const FrameBuffer * fr
|
|||||||
const uint8_t sw =
|
const uint8_t sw =
|
||||||
atomic_load_explicit(&texture->state.w, memory_order_acquire);
|
atomic_load_explicit(&texture->state.w, memory_order_acquire);
|
||||||
|
|
||||||
if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == sw + 1)
|
if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == (uint8_t)(sw + 1))
|
||||||
{
|
{
|
||||||
egl_warn_slow();
|
egl_warn_slow();
|
||||||
return true;
|
return true;
|
||||||
@@ -457,7 +457,7 @@ enum EGL_TexStatus egl_texture_bind(EGL_Texture * texture)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ss != sd && ss != sd+1)
|
if (ss != sd && ss != (uint8_t)(sd + 1))
|
||||||
sd = atomic_fetch_add_explicit(&texture->state.d, 1,
|
sd = atomic_fetch_add_explicit(&texture->state.d, 1,
|
||||||
memory_order_release) + 1;
|
memory_order_release) + 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -295,6 +295,12 @@ void opengl_deinitialize(void * opaque)
|
|||||||
free(this);
|
free(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void opengl_on_restart(void * opaque)
|
||||||
|
{
|
||||||
|
struct Inst * this = (struct Inst *)opaque;
|
||||||
|
this->waiting = true;
|
||||||
|
}
|
||||||
|
|
||||||
void opengl_on_resize(void * opaque, const int width, const int height, const LG_RendererRect destRect)
|
void opengl_on_resize(void * opaque, const int width, const int height, const LG_RendererRect destRect)
|
||||||
{
|
{
|
||||||
struct Inst * this = (struct Inst *)opaque;
|
struct Inst * this = (struct Inst *)opaque;
|
||||||
@@ -823,6 +829,7 @@ const LG_Renderer LGR_OpenGL =
|
|||||||
.create = opengl_create,
|
.create = opengl_create,
|
||||||
.initialize = opengl_initialize,
|
.initialize = opengl_initialize,
|
||||||
.deinitialize = opengl_deinitialize,
|
.deinitialize = opengl_deinitialize,
|
||||||
|
.on_restart = opengl_on_restart,
|
||||||
.on_resize = opengl_on_resize,
|
.on_resize = opengl_on_resize,
|
||||||
.on_mouse_shape = opengl_on_mouse_shape,
|
.on_mouse_shape = opengl_on_mouse_shape,
|
||||||
.on_mouse_event = opengl_on_mouse_event,
|
.on_mouse_event = opengl_on_mouse_event,
|
||||||
|
|||||||
@@ -247,6 +247,13 @@ static struct Option options[] =
|
|||||||
.type = OPTION_TYPE_INT,
|
.type = OPTION_TYPE_INT,
|
||||||
.value.x_int = 0,
|
.value.x_int = 0,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.module = "input",
|
||||||
|
.name = "mouseRedraw",
|
||||||
|
.description = "Mouse movements trigger redraws (ignores FPS minimum)",
|
||||||
|
.type = OPTION_TYPE_BOOL,
|
||||||
|
.value.x_bool = true,
|
||||||
|
},
|
||||||
|
|
||||||
// spice options
|
// spice options
|
||||||
{
|
{
|
||||||
@@ -399,6 +406,7 @@ bool config_load(int argc, char * argv[])
|
|||||||
params.escapeKey = option_get_int ("input", "escapeKey" );
|
params.escapeKey = option_get_int ("input", "escapeKey" );
|
||||||
params.hideMouse = option_get_bool ("input", "hideCursor" );
|
params.hideMouse = option_get_bool ("input", "hideCursor" );
|
||||||
params.mouseSens = option_get_int ("input", "mouseSens" );
|
params.mouseSens = option_get_int ("input", "mouseSens" );
|
||||||
|
params.mouseRedraw = option_get_bool ("input", "mouseRedraw" );
|
||||||
|
|
||||||
params.minimizeOnFocusLoss = option_get_bool("win", "minimizeOnFocusLoss");
|
params.minimizeOnFocusLoss = option_get_bool("win", "minimizeOnFocusLoss");
|
||||||
|
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ static int renderThread(void * unused)
|
|||||||
{
|
{
|
||||||
if (!state.lgr->render_startup(state.lgrData, state.window))
|
if (!state.lgr->render_startup(state.lgrData, state.window))
|
||||||
{
|
{
|
||||||
state.running = false;
|
state.state = APP_STATE_SHUTDOWN;
|
||||||
|
|
||||||
/* unblock threads waiting on the condition */
|
/* unblock threads waiting on the condition */
|
||||||
lgSignalEvent(e_startup);
|
lgSignalEvent(e_startup);
|
||||||
@@ -155,7 +155,7 @@ static int renderThread(void * unused)
|
|||||||
struct timespec time;
|
struct timespec time;
|
||||||
clock_gettime(CLOCK_REALTIME, &time);
|
clock_gettime(CLOCK_REALTIME, &time);
|
||||||
|
|
||||||
while(state.running)
|
while(state.state != APP_STATE_SHUTDOWN)
|
||||||
{
|
{
|
||||||
if (state.frameTime > 0)
|
if (state.frameTime > 0)
|
||||||
{
|
{
|
||||||
@@ -213,19 +213,23 @@ static int renderThread(void * unused)
|
|||||||
if (atomic_fetch_sub_explicit(&a_framesPending, 1, memory_order_release) > 1)
|
if (atomic_fetch_sub_explicit(&a_framesPending, 1, memory_order_release) > 1)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (lgWaitEventAbs(e_frame, &time))
|
if (lgWaitEventAbs(e_frame, &time) && state.frameTime > 0)
|
||||||
{
|
{
|
||||||
if (state.frameTime > 0)
|
/* only resync the timer if we got an early frame */
|
||||||
|
struct timespec now, diff;
|
||||||
|
clock_gettime(CLOCK_REALTIME, &now);
|
||||||
|
tsDiff(&diff, &now, &time);
|
||||||
|
if (diff.tv_sec == 0 && diff.tv_nsec < state.frameTime)
|
||||||
{
|
{
|
||||||
resyncCheck = 0;
|
resyncCheck = 0;
|
||||||
clock_gettime(CLOCK_REALTIME, &time);
|
memcpy(&time, &now, sizeof(struct timespec));
|
||||||
tsAdd(&time, state.frameTime);
|
tsAdd(&time, state.frameTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.running = false;
|
state.state = APP_STATE_SHUTDOWN;
|
||||||
|
|
||||||
if (t_cursor)
|
if (t_cursor)
|
||||||
lgJoinThread(t_cursor, NULL);
|
lgJoinThread(t_cursor, NULL);
|
||||||
@@ -247,7 +251,7 @@ static int cursorThread(void * unused)
|
|||||||
lgWaitEvent(e_startup, TIMEOUT_INFINITE);
|
lgWaitEvent(e_startup, TIMEOUT_INFINITE);
|
||||||
|
|
||||||
// subscribe to the pointer queue
|
// subscribe to the pointer queue
|
||||||
while(state.running)
|
while(state.state == APP_STATE_RUNNING)
|
||||||
{
|
{
|
||||||
status = lgmpClientSubscribe(state.lgmp, LGMP_Q_POINTER, &queue);
|
status = lgmpClientSubscribe(state.lgmp, LGMP_Q_POINTER, &queue);
|
||||||
if (status == LGMP_OK)
|
if (status == LGMP_OK)
|
||||||
@@ -260,11 +264,11 @@ static int cursorThread(void * unused)
|
|||||||
}
|
}
|
||||||
|
|
||||||
DEBUG_ERROR("lgmpClientSubscribe Failed: %s", lgmpStatusString(status));
|
DEBUG_ERROR("lgmpClientSubscribe Failed: %s", lgmpStatusString(status));
|
||||||
state.running = false;
|
state.state = APP_STATE_SHUTDOWN;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
while(state.running)
|
while(state.state == APP_STATE_RUNNING)
|
||||||
{
|
{
|
||||||
LGMPMessage msg;
|
LGMPMessage msg;
|
||||||
if ((status = lgmpClientProcess(queue, &msg)) != LGMP_OK)
|
if ((status = lgmpClientProcess(queue, &msg)) != LGMP_OK)
|
||||||
@@ -277,18 +281,25 @@ static int cursorThread(void * unused)
|
|||||||
state.lgr->on_mouse_event
|
state.lgr->on_mouse_event
|
||||||
(
|
(
|
||||||
state.lgrData,
|
state.lgrData,
|
||||||
state.cursorVisible && state.drawCursor && state.cursorInView,
|
state.cursorVisible && state.drawCursor,
|
||||||
state.cursor.x,
|
state.cursor.x,
|
||||||
state.cursor.y
|
state.cursor.y
|
||||||
);
|
);
|
||||||
|
|
||||||
|
lgSignalEvent(e_frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
usleep(params.cursorPollInterval);
|
usleep(params.cursorPollInterval);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (status == LGMP_ERR_INVALID_SESSION)
|
||||||
|
state.state = APP_STATE_RESTART;
|
||||||
|
else
|
||||||
|
{
|
||||||
DEBUG_ERROR("lgmpClientProcess Failed: %s", lgmpStatusString(status));
|
DEBUG_ERROR("lgmpClientProcess Failed: %s", lgmpStatusString(status));
|
||||||
state.running = false;
|
state.state = APP_STATE_SHUTDOWN;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,19 +308,6 @@ static int cursorThread(void * unused)
|
|||||||
state.cursorVisible =
|
state.cursorVisible =
|
||||||
msg.udata & CURSOR_FLAG_VISIBLE;
|
msg.udata & CURSOR_FLAG_VISIBLE;
|
||||||
|
|
||||||
if (msg.udata & CURSOR_FLAG_POSITION)
|
|
||||||
{
|
|
||||||
state.cursor.x = cursor->x;
|
|
||||||
state.cursor.y = cursor->y;
|
|
||||||
state.haveCursorPos = true;
|
|
||||||
|
|
||||||
if (!state.haveAligned && state.haveSrcSize && state.haveCurLocal)
|
|
||||||
{
|
|
||||||
alignMouseWithHost();
|
|
||||||
state.haveAligned = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.udata & CURSOR_FLAG_SHAPE)
|
if (msg.udata & CURSOR_FLAG_SHAPE)
|
||||||
{
|
{
|
||||||
switch(cursor->type)
|
switch(cursor->type)
|
||||||
@@ -323,6 +321,9 @@ static int cursorThread(void * unused)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state.cursor.hx = cursor->hx;
|
||||||
|
state.cursor.hy = cursor->hy;
|
||||||
|
|
||||||
const uint8_t * data = (const uint8_t *)(cursor + 1);
|
const uint8_t * data = (const uint8_t *)(cursor + 1);
|
||||||
if (!state.lgr->on_mouse_shape(
|
if (!state.lgr->on_mouse_shape(
|
||||||
state.lgrData,
|
state.lgrData,
|
||||||
@@ -339,6 +340,16 @@ static int cursorThread(void * unused)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (msg.udata & CURSOR_FLAG_POSITION)
|
||||||
|
{
|
||||||
|
state.cursor.x = cursor->x;
|
||||||
|
state.cursor.y = cursor->y;
|
||||||
|
state.haveCursorPos = true;
|
||||||
|
|
||||||
|
if (state.haveSrcSize && state.haveCurLocal && !state.serverMode)
|
||||||
|
alignMouseWithGuest();
|
||||||
|
}
|
||||||
|
|
||||||
lgmpClientMessageDone(queue);
|
lgmpClientMessageDone(queue);
|
||||||
state.updateCursor = false;
|
state.updateCursor = false;
|
||||||
|
|
||||||
@@ -349,10 +360,12 @@ static int cursorThread(void * unused)
|
|||||||
state.cursor.x,
|
state.cursor.x,
|
||||||
state.cursor.y
|
state.cursor.y
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (params.mouseRedraw)
|
||||||
|
lgSignalEvent(e_frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
lgmpClientUnsubscribe(&queue);
|
lgmpClientUnsubscribe(&queue);
|
||||||
state.running = false;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,11 +376,11 @@ static int frameThread(void * unused)
|
|||||||
|
|
||||||
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
|
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
|
||||||
lgWaitEvent(e_startup, TIMEOUT_INFINITE);
|
lgWaitEvent(e_startup, TIMEOUT_INFINITE);
|
||||||
if (!state.running)
|
if (state.state != APP_STATE_RUNNING)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
// subscribe to the frame queue
|
// subscribe to the frame queue
|
||||||
while(state.running)
|
while(state.state == APP_STATE_RUNNING)
|
||||||
{
|
{
|
||||||
status = lgmpClientSubscribe(state.lgmp, LGMP_Q_FRAME, &queue);
|
status = lgmpClientSubscribe(state.lgmp, LGMP_Q_FRAME, &queue);
|
||||||
if (status == LGMP_OK)
|
if (status == LGMP_OK)
|
||||||
@@ -380,11 +393,11 @@ static int frameThread(void * unused)
|
|||||||
}
|
}
|
||||||
|
|
||||||
DEBUG_ERROR("lgmpClientSubscribe Failed: %s", lgmpStatusString(status));
|
DEBUG_ERROR("lgmpClientSubscribe Failed: %s", lgmpStatusString(status));
|
||||||
state.running = false;
|
state.state = APP_STATE_SHUTDOWN;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
while(state.running)
|
while(state.state == APP_STATE_RUNNING)
|
||||||
{
|
{
|
||||||
LGMPMessage msg;
|
LGMPMessage msg;
|
||||||
if ((status = lgmpClientProcess(queue, &msg)) != LGMP_OK)
|
if ((status = lgmpClientProcess(queue, &msg)) != LGMP_OK)
|
||||||
@@ -395,7 +408,13 @@ static int frameThread(void * unused)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (status == LGMP_ERR_INVALID_SESSION)
|
||||||
|
state.state = APP_STATE_RESTART;
|
||||||
|
else
|
||||||
|
{
|
||||||
DEBUG_ERROR("lgmpClientProcess Failed: %s", lgmpStatusString(status));
|
DEBUG_ERROR("lgmpClientProcess Failed: %s", lgmpStatusString(status));
|
||||||
|
state.state = APP_STATE_SHUTDOWN;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,6 +454,7 @@ static int frameThread(void * unused)
|
|||||||
if (error)
|
if (error)
|
||||||
{
|
{
|
||||||
lgmpClientMessageDone(queue);
|
lgmpClientMessageDone(queue);
|
||||||
|
state.state = APP_STATE_SHUTDOWN;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,6 +473,7 @@ static int frameThread(void * unused)
|
|||||||
if (!state.lgr->on_frame_event(state.lgrData, lgrFormat, fb))
|
if (!state.lgr->on_frame_event(state.lgrData, lgrFormat, fb))
|
||||||
{
|
{
|
||||||
DEBUG_ERROR("renderer on frame event returned failure");
|
DEBUG_ERROR("renderer on frame event returned failure");
|
||||||
|
state.state = APP_STATE_SHUTDOWN;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,24 +485,23 @@ static int frameThread(void * unused)
|
|||||||
}
|
}
|
||||||
|
|
||||||
lgmpClientUnsubscribe(&queue);
|
lgmpClientUnsubscribe(&queue);
|
||||||
state.running = false;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int spiceThread(void * arg)
|
int spiceThread(void * arg)
|
||||||
{
|
{
|
||||||
while(state.running)
|
while(state.state != APP_STATE_SHUTDOWN)
|
||||||
if (!spice_process(1000))
|
if (!spice_process(1000))
|
||||||
{
|
{
|
||||||
if (state.running)
|
if (state.state != APP_STATE_SHUTDOWN)
|
||||||
{
|
{
|
||||||
state.running = false;
|
state.state = APP_STATE_SHUTDOWN;
|
||||||
DEBUG_ERROR("failed to process spice messages");
|
DEBUG_ERROR("failed to process spice messages");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.running = false;
|
state.state = APP_STATE_SHUTDOWN;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -657,10 +677,22 @@ void spiceClipboardRequest(const SpiceDataType type)
|
|||||||
state.lgc->request(spice_type_to_clipboard_type(type));
|
state.lgc->request(spice_type_to_clipboard_type(type));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void warpMouse(int x, int y)
|
||||||
|
{
|
||||||
|
if (state.warpState != WARP_STATE_ON)
|
||||||
|
return;
|
||||||
|
|
||||||
|
state.warpFromX = state.curLastX;
|
||||||
|
state.warpFromY = state.curLastY;
|
||||||
|
state.warpToX = x;
|
||||||
|
state.warpToY = y;
|
||||||
|
state.warpState = WARP_STATE_ACTIVE;
|
||||||
|
|
||||||
|
SDL_WarpMouseInWindow(state.window, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
static void handleMouseMoveEvent(int ex, int ey)
|
static void handleMouseMoveEvent(int ex, int ey)
|
||||||
{
|
{
|
||||||
static bool wrapping = false;
|
|
||||||
static int wrapX, wrapY;
|
|
||||||
|
|
||||||
state.curLocalX = ex;
|
state.curLocalX = ex;
|
||||||
state.curLocalY = ey;
|
state.curLocalY = ey;
|
||||||
@@ -669,28 +701,23 @@ static void handleMouseMoveEvent(int ex, int ey)
|
|||||||
if (state.ignoreInput || !params.useSpiceInput)
|
if (state.ignoreInput || !params.useSpiceInput)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (state.warpState == WARP_STATE_ACTIVE)
|
||||||
|
{
|
||||||
|
if (ex == state.warpToX && ey == state.warpToY)
|
||||||
|
{
|
||||||
|
state.curLastX += state.warpToX - state.warpFromX;
|
||||||
|
state.curLastY += state.warpToY - state.warpFromY;
|
||||||
|
state.warpState = WARP_STATE_ON;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (state.serverMode)
|
if (state.serverMode)
|
||||||
{
|
|
||||||
if (wrapping)
|
|
||||||
{
|
|
||||||
if (ex == state.windowW / 2 && ey == state.windowH / 2)
|
|
||||||
{
|
|
||||||
state.curLastX += (state.windowW / 2) - wrapX;
|
|
||||||
state.curLastY += (state.windowH / 2) - wrapY;
|
|
||||||
wrapping = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
if (
|
if (
|
||||||
ex < 100 || ex > state.windowW - 100 ||
|
ex < 100 || ex > state.windowW - 100 ||
|
||||||
ey < 100 || ey > state.windowH - 100)
|
ey < 100 || ey > state.windowH - 100)
|
||||||
{
|
{
|
||||||
wrapping = true;
|
warpMouse(state.windowW / 2, state.windowH / 2);
|
||||||
wrapX = ex;
|
|
||||||
wrapY = ey;
|
|
||||||
SDL_WarpMouseInWindow(state.window, state.windowW / 2, state.windowH / 2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -702,6 +729,10 @@ static void handleMouseMoveEvent(int ex, int ey)
|
|||||||
{
|
{
|
||||||
state.cursorInView = false;
|
state.cursorInView = false;
|
||||||
state.updateCursor = true;
|
state.updateCursor = true;
|
||||||
|
state.warpState = WARP_STATE_OFF;
|
||||||
|
|
||||||
|
if (params.useSpiceInput)
|
||||||
|
state.drawCursor = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -710,6 +741,9 @@ static void handleMouseMoveEvent(int ex, int ey)
|
|||||||
{
|
{
|
||||||
state.cursorInView = true;
|
state.cursorInView = true;
|
||||||
state.updateCursor = true;
|
state.updateCursor = true;
|
||||||
|
state.drawCursor = true;
|
||||||
|
if (state.warpState == WARP_STATE_ARMED)
|
||||||
|
state.warpState = WARP_STATE_ON;
|
||||||
}
|
}
|
||||||
|
|
||||||
int rx = ex - state.curLastX;
|
int rx = ex - state.curLastX;
|
||||||
@@ -749,9 +783,9 @@ static void alignMouseWithGuest()
|
|||||||
if (state.ignoreInput || !params.useSpiceInput)
|
if (state.ignoreInput || !params.useSpiceInput)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
state.curLastX = (int)round((float)state.cursor.x / state.scaleX) + state.dstRect.x;
|
state.curLastX = (int)round((float)(state.cursor.x + state.cursor.hx) / state.scaleX) + state.dstRect.x;
|
||||||
state.curLastY = (int)round((float)state.cursor.y / state.scaleY) + state.dstRect.y;
|
state.curLastY = (int)round((float)(state.cursor.y + state.cursor.hy) / state.scaleY) + state.dstRect.y;
|
||||||
SDL_WarpMouseInWindow(state.window, state.curLastX, state.curLastY);
|
warpMouse(state.curLastX, state.curLastY);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void alignMouseWithHost()
|
static void alignMouseWithHost()
|
||||||
@@ -762,8 +796,8 @@ static void alignMouseWithHost()
|
|||||||
if (!state.haveCursorPos || state.serverMode)
|
if (!state.haveCursorPos || state.serverMode)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
state.curLastX = (int)round((float)state.cursor.x / state.scaleX) + state.dstRect.x;
|
state.curLastX = (int)round((float)(state.cursor.x + state.cursor.hx) / state.scaleX) + state.dstRect.x;
|
||||||
state.curLastY = (int)round((float)state.cursor.y / state.scaleY) + state.dstRect.y;
|
state.curLastY = (int)round((float)(state.cursor.y + state.cursor.hy) / state.scaleY) + state.dstRect.y;
|
||||||
handleMouseMoveEvent(state.curLocalX, state.curLocalY);
|
handleMouseMoveEvent(state.curLocalX, state.curLocalY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -785,6 +819,7 @@ static void handleWindowLeave()
|
|||||||
state.drawCursor = false;
|
state.drawCursor = false;
|
||||||
state.cursorInView = false;
|
state.cursorInView = false;
|
||||||
state.updateCursor = true;
|
state.updateCursor = true;
|
||||||
|
state.warpState = WARP_STATE_OFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void handleWindowEnter()
|
static void handleWindowEnter()
|
||||||
@@ -795,6 +830,7 @@ static void handleWindowEnter()
|
|||||||
alignMouseWithHost();
|
alignMouseWithHost();
|
||||||
state.drawCursor = true;
|
state.drawCursor = true;
|
||||||
state.updateCursor = true;
|
state.updateCursor = true;
|
||||||
|
state.warpState = WARP_STATE_ARMED;
|
||||||
}
|
}
|
||||||
|
|
||||||
int eventFilter(void * userdata, SDL_Event * event)
|
int eventFilter(void * userdata, SDL_Event * event)
|
||||||
@@ -806,7 +842,7 @@ int eventFilter(void * userdata, SDL_Event * event)
|
|||||||
if (!params.ignoreQuit)
|
if (!params.ignoreQuit)
|
||||||
{
|
{
|
||||||
DEBUG_INFO("Quit event received, exiting...");
|
DEBUG_INFO("Quit event received, exiting...");
|
||||||
state.running = false;
|
state.state = APP_STATE_SHUTDOWN;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -833,7 +869,7 @@ int eventFilter(void * userdata, SDL_Event * event)
|
|||||||
|
|
||||||
// allow a window close event to close the application even if ignoreQuit is set
|
// allow a window close event to close the application even if ignoreQuit is set
|
||||||
case SDL_WINDOWEVENT_CLOSE:
|
case SDL_WINDOWEVENT_CLOSE:
|
||||||
state.running = false;
|
state.state = APP_STATE_SHUTDOWN;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
@@ -937,7 +973,9 @@ int eventFilter(void * userdata, SDL_Event * event)
|
|||||||
state.serverMode ? "Capture Enabled" : "Capture Disabled"
|
state.serverMode ? "Capture Enabled" : "Capture Disabled"
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!state.serverMode)
|
if (state.serverMode)
|
||||||
|
state.warpState = WARP_STATE_ON;
|
||||||
|
else
|
||||||
alignMouseWithGuest();
|
alignMouseWithGuest();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1033,7 +1071,7 @@ void int_handler(int signal)
|
|||||||
case SIGINT:
|
case SIGINT:
|
||||||
case SIGTERM:
|
case SIGTERM:
|
||||||
DEBUG_INFO("Caught signal, shutting down...");
|
DEBUG_INFO("Caught signal, shutting down...");
|
||||||
state.running = false;
|
state.state = APP_STATE_SHUTDOWN;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1081,7 +1119,7 @@ static void toggle_input(SDL_Scancode key, void * opaque)
|
|||||||
|
|
||||||
static void quit(SDL_Scancode key, void * opaque)
|
static void quit(SDL_Scancode key, void * opaque)
|
||||||
{
|
{
|
||||||
state.running = false;
|
state.state = APP_STATE_SHUTDOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void mouse_sens_inc(SDL_Scancode key, void * opaque)
|
static void mouse_sens_inc(SDL_Scancode key, void * opaque)
|
||||||
@@ -1164,7 +1202,7 @@ static void release_key_binds()
|
|||||||
static int lg_run()
|
static int lg_run()
|
||||||
{
|
{
|
||||||
memset(&state, 0, sizeof(state));
|
memset(&state, 0, sizeof(state));
|
||||||
state.running = true;
|
state.state = APP_STATE_RUNNING;
|
||||||
state.scaleX = 1.0f;
|
state.scaleX = 1.0f;
|
||||||
state.scaleY = 1.0f;
|
state.scaleY = 1.0f;
|
||||||
state.resizeDone = true;
|
state.resizeDone = true;
|
||||||
@@ -1227,10 +1265,10 @@ static int lg_run()
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
while(state.running && !spice_ready())
|
while(state.state != APP_STATE_SHUTDOWN && !spice_ready())
|
||||||
if (!spice_process(1000))
|
if (!spice_process(1000))
|
||||||
{
|
{
|
||||||
state.running = false;
|
state.state = APP_STATE_SHUTDOWN;
|
||||||
DEBUG_ERROR("Failed to process spice messages");
|
DEBUG_ERROR("Failed to process spice messages");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -1320,13 +1358,15 @@ static int lg_run()
|
|||||||
// ensure renderer viewport is aware of the current window size
|
// ensure renderer viewport is aware of the current window size
|
||||||
updatePositionInfo();
|
updatePositionInfo();
|
||||||
|
|
||||||
// use a default of 60FPS now that frame updates are host update triggered
|
|
||||||
if (params.fpsMin == -1)
|
if (params.fpsMin == -1)
|
||||||
state.frameTime = 1e9 / 60;
|
{
|
||||||
|
// minimum 60fps to keep interactivity decent
|
||||||
|
state.frameTime = 1000000000ULL / 60ULL;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DEBUG_INFO("Using the FPS minimum from args: %d", params.fpsMin);
|
DEBUG_INFO("Using the FPS minimum from args: %d", params.fpsMin);
|
||||||
state.frameTime = 1e9 / params.fpsMin;
|
state.frameTime = 1000000000ULL / (unsigned long long)params.fpsMin;
|
||||||
}
|
}
|
||||||
|
|
||||||
register_key_binds();
|
register_key_binds();
|
||||||
@@ -1442,7 +1482,7 @@ static int lg_run()
|
|||||||
|
|
||||||
LGMP_STATUS status;
|
LGMP_STATUS status;
|
||||||
|
|
||||||
while(state.running)
|
while(state.state == APP_STATE_RUNNING)
|
||||||
{
|
{
|
||||||
if ((status = lgmpClientInit(state.shm.mem, state.shm.size, &state.lgmp)) == LGMP_OK)
|
if ((status = lgmpClientInit(state.shm.mem, state.shm.size, &state.lgmp)) == LGMP_OK)
|
||||||
break;
|
break;
|
||||||
@@ -1457,9 +1497,10 @@ static int lg_run()
|
|||||||
|
|
||||||
uint32_t udataSize;
|
uint32_t udataSize;
|
||||||
KVMFR *udata;
|
KVMFR *udata;
|
||||||
|
|
||||||
int waitCount = 0;
|
int waitCount = 0;
|
||||||
while(state.running)
|
|
||||||
|
restart:
|
||||||
|
while(state.state == APP_STATE_RUNNING)
|
||||||
{
|
{
|
||||||
if ((status = lgmpClientSessionInit(state.lgmp, &udataSize, (uint8_t **)&udata)) == LGMP_OK)
|
if ((status = lgmpClientSessionInit(state.lgmp, &udataSize, (uint8_t **)&udata)) == LGMP_OK)
|
||||||
break;
|
break;
|
||||||
@@ -1488,9 +1529,12 @@ static int lg_run()
|
|||||||
SDL_WaitEventTimeout(NULL, 1000);
|
SDL_WaitEventTimeout(NULL, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!state.running)
|
if (state.state != APP_STATE_RUNNING)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
// dont show warnings again after the first startup
|
||||||
|
waitCount = 100;
|
||||||
|
|
||||||
const bool magicMatches = memcmp(udata->magic, KVMFR_MAGIC, sizeof(udata->magic)) == 0;
|
const bool magicMatches = memcmp(udata->magic, KVMFR_MAGIC, sizeof(udata->magic)) == 0;
|
||||||
if (udataSize != sizeof(KVMFR) || !magicMatches || udata->version != KVMFR_VERSION)
|
if (udataSize != sizeof(KVMFR) || !magicMatches || udata->version != KVMFR_VERSION)
|
||||||
{
|
{
|
||||||
@@ -1527,14 +1571,30 @@ static int lg_run()
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
while(state.running)
|
while(state.state == APP_STATE_RUNNING)
|
||||||
{
|
{
|
||||||
if (!lgmpClientSessionValid(state.lgmp))
|
if (!lgmpClientSessionValid(state.lgmp))
|
||||||
{
|
{
|
||||||
DEBUG_WARN("Session is invalid, has the host shutdown?");
|
state.state = APP_STATE_RESTART;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
SDL_WaitEventTimeout(NULL, 1000);
|
SDL_WaitEventTimeout(NULL, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.state == APP_STATE_RESTART)
|
||||||
|
{
|
||||||
|
lgSignalEvent(e_startup);
|
||||||
|
lgSignalEvent(e_frame);
|
||||||
|
lgJoinThread(t_frame , NULL);
|
||||||
|
lgJoinThread(t_cursor, NULL);
|
||||||
|
t_frame = NULL;
|
||||||
|
t_cursor = NULL;
|
||||||
|
|
||||||
|
state.state = APP_STATE_RUNNING;
|
||||||
|
state.lgr->on_restart(state.lgrData);
|
||||||
|
|
||||||
|
DEBUG_INFO("Waiting for the host to restart...");
|
||||||
|
goto restart;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -1542,8 +1602,7 @@ static int lg_run()
|
|||||||
|
|
||||||
static void lg_shutdown()
|
static void lg_shutdown()
|
||||||
{
|
{
|
||||||
state.running = false;
|
state.state = APP_STATE_SHUTDOWN;
|
||||||
|
|
||||||
if (t_render)
|
if (t_render)
|
||||||
{
|
{
|
||||||
lgSignalEvent(e_startup);
|
lgSignalEvent(e_startup);
|
||||||
|
|||||||
@@ -29,9 +29,30 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
|||||||
#include "spice/spice.h"
|
#include "spice/spice.h"
|
||||||
#include <lgmp/client.h>
|
#include <lgmp/client.h>
|
||||||
|
|
||||||
|
enum RunState
|
||||||
|
{
|
||||||
|
APP_STATE_RUNNING,
|
||||||
|
APP_STATE_RESTART,
|
||||||
|
APP_STATE_SHUTDOWN
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CursorInfo
|
||||||
|
{
|
||||||
|
int x , y;
|
||||||
|
int hx, hy;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum WarpState
|
||||||
|
{
|
||||||
|
WARP_STATE_ARMED,
|
||||||
|
WARP_STATE_ON,
|
||||||
|
WARP_STATE_ACTIVE,
|
||||||
|
WARP_STATE_OFF
|
||||||
|
};
|
||||||
|
|
||||||
struct AppState
|
struct AppState
|
||||||
{
|
{
|
||||||
bool running;
|
enum RunState state;
|
||||||
bool ignoreInput;
|
bool ignoreInput;
|
||||||
bool escapeActive;
|
bool escapeActive;
|
||||||
SDL_Scancode escapeAction;
|
SDL_Scancode escapeAction;
|
||||||
@@ -42,7 +63,7 @@ struct AppState
|
|||||||
int windowW, windowH;
|
int windowW, windowH;
|
||||||
SDL_Point srcSize;
|
SDL_Point srcSize;
|
||||||
LG_RendererRect dstRect;
|
LG_RendererRect dstRect;
|
||||||
SDL_Point cursor;
|
struct CursorInfo cursor;
|
||||||
bool cursorVisible;
|
bool cursorVisible;
|
||||||
|
|
||||||
bool serverMode;
|
bool serverMode;
|
||||||
@@ -60,6 +81,10 @@ struct AppState
|
|||||||
int curLocalY;
|
int curLocalY;
|
||||||
bool haveAligned;
|
bool haveAligned;
|
||||||
|
|
||||||
|
enum WarpState warpState;
|
||||||
|
int warpFromX, warpFromY;
|
||||||
|
int warpToX , warpToY;
|
||||||
|
|
||||||
const LG_Renderer * lgr;
|
const LG_Renderer * lgr;
|
||||||
void * lgrData;
|
void * lgrData;
|
||||||
bool lgrResize;
|
bool lgrResize;
|
||||||
@@ -135,6 +160,7 @@ struct AppParams
|
|||||||
|
|
||||||
const char * windowTitle;
|
const char * windowTitle;
|
||||||
int mouseSens;
|
int mouseSens;
|
||||||
|
bool mouseRedraw;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CBRequest
|
struct CBRequest
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ include_directories(
|
|||||||
${PROJECT_SOURCE_DIR}/include
|
${PROJECT_SOURCE_DIR}/include
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_definitions(-D_GNU_SOURCE)
|
||||||
|
|
||||||
if(ENABLE_BACKTRACE)
|
if(ENABLE_BACKTRACE)
|
||||||
add_definitions(-DENABLE_BACKTRACE)
|
add_definitions(-DENABLE_BACKTRACE)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ typedef enum CursorType
|
|||||||
CursorType;
|
CursorType;
|
||||||
|
|
||||||
#define KVMFR_MAGIC "KVMFR---"
|
#define KVMFR_MAGIC "KVMFR---"
|
||||||
#define KVMFR_VERSION 2
|
#define KVMFR_VERSION 3
|
||||||
|
|
||||||
typedef struct KVMFR
|
typedef struct KVMFR
|
||||||
{
|
{
|
||||||
@@ -65,6 +65,7 @@ typedef struct KVMFRCursor
|
|||||||
{
|
{
|
||||||
int16_t x, y; // cursor x & y position
|
int16_t x, y; // cursor x & y position
|
||||||
CursorType type; // shape buffer data type
|
CursorType type; // shape buffer data type
|
||||||
|
int8_t hx, hy; // shape hotspot x & y
|
||||||
uint32_t width; // width of the shape
|
uint32_t width; // width of the shape
|
||||||
uint32_t height; // height of the shape
|
uint32_t height; // height of the shape
|
||||||
uint32_t pitch; // row length in bytes of the shape
|
uint32_t pitch; // row length in bytes of the shape
|
||||||
|
|||||||
@@ -24,8 +24,10 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
|||||||
#include <stdatomic.h>
|
#include <stdatomic.h>
|
||||||
#include <emmintrin.h>
|
#include <emmintrin.h>
|
||||||
#include <smmintrin.h>
|
#include <smmintrin.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
#define FB_CHUNK_SIZE 1048576
|
#define FB_CHUNK_SIZE 1048576 // 1MB
|
||||||
|
#define FB_SPIN_LIMIT 10000 // 10ms
|
||||||
|
|
||||||
struct stFrameBuffer
|
struct stFrameBuffer
|
||||||
{
|
{
|
||||||
@@ -40,7 +42,6 @@ void framebuffer_wait(const FrameBuffer * frame, size_t size)
|
|||||||
while(atomic_load_explicit(&frame->wp, memory_order_acquire) != size) {}
|
while(atomic_load_explicit(&frame->wp, memory_order_acquire) != size) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool framebuffer_read(const FrameBuffer * frame, void * restrict dst,
|
bool framebuffer_read(const FrameBuffer * frame, void * restrict dst,
|
||||||
size_t dstpitch, size_t height, size_t width, size_t bpp, size_t pitch)
|
size_t dstpitch, size_t height, size_t width, size_t bpp, size_t pitch)
|
||||||
{
|
{
|
||||||
@@ -54,11 +55,18 @@ bool framebuffer_read(const FrameBuffer * frame, void * restrict dst,
|
|||||||
while(y < height)
|
while(y < height)
|
||||||
{
|
{
|
||||||
uint_least32_t wp;
|
uint_least32_t wp;
|
||||||
|
int spinCount = 0;
|
||||||
|
|
||||||
/* spinlock */
|
/* spinlock */
|
||||||
do
|
|
||||||
wp = atomic_load_explicit(&frame->wp, memory_order_acquire);
|
wp = atomic_load_explicit(&frame->wp, memory_order_acquire);
|
||||||
while(wp - rp < pitch);
|
while(wp - rp < linewidth)
|
||||||
|
{
|
||||||
|
if (++spinCount == FB_SPIN_LIMIT)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
usleep(1);
|
||||||
|
wp = atomic_load_explicit(&frame->wp, memory_order_acquire);
|
||||||
|
}
|
||||||
|
|
||||||
_mm_mfence();
|
_mm_mfence();
|
||||||
__m128i * restrict s = (__m128i *)(frame->data + rp);
|
__m128i * restrict s = (__m128i *)(frame->data + rp);
|
||||||
@@ -104,11 +112,18 @@ bool framebuffer_read_fn(const FrameBuffer * frame, size_t height, size_t width,
|
|||||||
while(y < height)
|
while(y < height)
|
||||||
{
|
{
|
||||||
uint_least32_t wp;
|
uint_least32_t wp;
|
||||||
|
int spinCount = 0;
|
||||||
|
|
||||||
/* spinlock */
|
/* spinlock */
|
||||||
do
|
|
||||||
wp = atomic_load_explicit(&frame->wp, memory_order_acquire);
|
wp = atomic_load_explicit(&frame->wp, memory_order_acquire);
|
||||||
while(wp - rp < pitch);
|
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))
|
if (!fn(opaque, frame->data + rp, linewidth))
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
|||||||
Place, Suite 330, Boston, MA 02111-1307 USA
|
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define _GNU_SOURCE
|
|
||||||
#include "common/crash.h"
|
#include "common/crash.h"
|
||||||
#include "common/debug.h"
|
#include "common/debug.h"
|
||||||
|
|
||||||
|
|||||||
@@ -83,19 +83,20 @@ bool lgWaitEventAbs(LGEvent * handle, struct timespec * ts)
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool ret = true;
|
bool ret = true;
|
||||||
|
int res;
|
||||||
while(ret && !atomic_load(&handle->flag))
|
while(ret && !atomic_load(&handle->flag))
|
||||||
{
|
{
|
||||||
if (!ts)
|
if (!ts)
|
||||||
{
|
{
|
||||||
if (pthread_cond_wait(&handle->cond, &handle->mutex) != 0)
|
if ((res = pthread_cond_wait(&handle->cond, &handle->mutex)) != 0)
|
||||||
{
|
{
|
||||||
DEBUG_ERROR("Wait to wait on the condition");
|
DEBUG_ERROR("Failed to wait on the condition (err: %d)", res);
|
||||||
ret = false;
|
ret = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
switch(pthread_cond_timedwait(&handle->cond, &handle->mutex, ts))
|
switch((res = pthread_cond_timedwait(&handle->cond, &handle->mutex, ts)))
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
break;
|
break;
|
||||||
@@ -106,7 +107,7 @@ bool lgWaitEventAbs(LGEvent * handle, struct timespec * ts)
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
ret = false;
|
ret = false;
|
||||||
DEBUG_ERROR("Timed wait failed");
|
DEBUG_ERROR("Timed wait failed (err: %d)", res);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,6 +146,9 @@ bool lgWaitEventNS(LGEvent * handle, unsigned int timeout)
|
|||||||
|
|
||||||
bool lgWaitEvent(LGEvent * handle, unsigned int timeout)
|
bool lgWaitEvent(LGEvent * handle, unsigned int timeout)
|
||||||
{
|
{
|
||||||
|
if (timeout == TIMEOUT_INFINITE)
|
||||||
|
return lgWaitEventAbs(handle, NULL);
|
||||||
|
|
||||||
return lgWaitEventNS(handle, timeout * 1000000);
|
return lgWaitEventNS(handle, timeout * 1000000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ bool lgCreateThread(const char * name, LGThreadFunction function, void * opaque,
|
|||||||
*handle = NULL;
|
*handle = NULL;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pthread_setname_np((*handle)->handle, name);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ Currently only Windows is supported however there is some initial support for Li
|
|||||||
6. configure the project and build it
|
6. configure the project and build it
|
||||||
|
|
||||||
```
|
```
|
||||||
mkdir LookingGlass/c-host/build
|
mkdir LookingGlass/host/build
|
||||||
cd LookingGlass/c-host/build
|
cd LookingGlass/host/build
|
||||||
cmake -G "MSYS Makefiles" ..
|
cmake -G "MSYS Makefiles" ..
|
||||||
make
|
make
|
||||||
```
|
```
|
||||||
@@ -44,17 +44,46 @@ cmake -DCMAKE_TOOLCHAIN_FILE=../toolchain-mingw64.cmake ..
|
|||||||
make
|
make
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Building the Windows installer
|
||||||
|
|
||||||
|
Install NSIS compiler
|
||||||
|
Build the host program, see above sections.
|
||||||
|
Build installer with `makensis platform/Windows/installer.nsi`
|
||||||
|
The resulting installer will be at
|
||||||
|
`platform/Windows/looking-glass-host-setup.exe`
|
||||||
|
|
||||||
## Where is the log?
|
## Where is the log?
|
||||||
|
|
||||||
It is in your user's temp directory:
|
It is in your user's temp directory:
|
||||||
|
|
||||||
%TEMP%\looking-glass-host.txt
|
%TEMP%\looking-glass-host.txt
|
||||||
|
|
||||||
For example:
|
Or if running as a system service it will be located in:
|
||||||
|
|
||||||
C:\Users\YourUser\AppData\Local\Temp\looking-glass-host.txt
|
C:\Windows\Temp\looking-glass-host.txt
|
||||||
|
|
||||||
You can also open it by simply right clicking the tray icon and selecting "Open Log File"
|
You can find out where the file is by right clicking on the tray icon and
|
||||||
|
selecting "Log File Location"
|
||||||
|
|
||||||
|
### High priority capture using DXGI and Secure Desktop (UAC) capture support
|
||||||
|
|
||||||
|
By default Windows gives priority to the foreground application for any GPU
|
||||||
|
work which causes issues with capture if the foreground application is consuming
|
||||||
|
100% of the available GPU resources. The looking glass host application is able
|
||||||
|
to increase the kernel GPU thread to realtime priority which fixes this, but in
|
||||||
|
order to do so it must run as the `SYSTEM` user account. To do this, Looking
|
||||||
|
Glass needs to run as a service. This can be accomplished by either using the
|
||||||
|
NSIS installer which will do this for you, or you can use the following command
|
||||||
|
to Install the service manually:
|
||||||
|
|
||||||
|
looking-glass-host.exe InstallService
|
||||||
|
|
||||||
|
To remove the service use the following command:
|
||||||
|
|
||||||
|
looking-glass-host.exe UninstallService
|
||||||
|
|
||||||
|
This will also enable the host application to capture the secure desktop which
|
||||||
|
includes things like the lock screen and UAC prompts.
|
||||||
|
|
||||||
## Why does this version require Administrator privileges
|
## Why does this version require Administrator privileges
|
||||||
|
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ typedef struct CapturePointer
|
|||||||
|
|
||||||
bool shapeUpdate;
|
bool shapeUpdate;
|
||||||
CaptureFormat format;
|
CaptureFormat format;
|
||||||
|
unsigned int hx, hy;
|
||||||
unsigned int width, height;
|
unsigned int width, height;
|
||||||
unsigned int pitch;
|
unsigned int pitch;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ include_directories(
|
|||||||
|
|
||||||
add_library(platform_Windows STATIC
|
add_library(platform_Windows STATIC
|
||||||
src/platform.c
|
src/platform.c
|
||||||
|
src/service.c
|
||||||
src/mousehook.c
|
src/mousehook.c
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -23,9 +24,19 @@ target_link_libraries(platform_Windows
|
|||||||
"${PROJECT_BINARY_DIR}/resource.o"
|
"${PROJECT_BINARY_DIR}/resource.o"
|
||||||
lg_common
|
lg_common
|
||||||
capture
|
capture
|
||||||
|
|
||||||
|
userenv
|
||||||
|
wtsapi32
|
||||||
|
psapi
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(platform_Windows
|
target_include_directories(platform_Windows
|
||||||
PRIVATE
|
PRIVATE
|
||||||
src
|
src
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# these are for the nsis installer generator
|
||||||
|
configure_file("${PROJECT_SOURCE_DIR}/installer.nsi" "${PROJECT_BINARY_DIR}/installer.nsi" COPYONLY)
|
||||||
|
configure_file("${PROJECT_TOP}/resources/icon.ico" "${PROJECT_BINARY_DIR}/icon.ico" COPYONLY)
|
||||||
|
configure_file("${PROJECT_TOP}/VERSION" "${PROJECT_BINARY_DIR}/VERSION" COPYONLY)
|
||||||
|
configure_file("${PROJECT_TOP}/LICENSE" "${PROJECT_BINARY_DIR}/LICENSE.txt" COPYONLY)
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
|||||||
#include "common/event.h"
|
#include "common/event.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <stdatomic.h>
|
||||||
|
#include <unistd.h>
|
||||||
#include <dxgi.h>
|
#include <dxgi.h>
|
||||||
#include <d3d11.h>
|
#include <d3d11.h>
|
||||||
#include <d3dcommon.h>
|
#include <d3dcommon.h>
|
||||||
@@ -55,7 +57,7 @@ enum TextureState
|
|||||||
|
|
||||||
typedef struct Texture
|
typedef struct Texture
|
||||||
{
|
{
|
||||||
enum TextureState state;
|
volatile enum TextureState state;
|
||||||
ID3D11Texture2D * tex;
|
ID3D11Texture2D * tex;
|
||||||
D3D11_MAPPED_SUBRESOURCE map;
|
D3D11_MAPPED_SUBRESOURCE map;
|
||||||
}
|
}
|
||||||
@@ -82,7 +84,7 @@ struct iface
|
|||||||
Texture * texture;
|
Texture * texture;
|
||||||
int texRIndex;
|
int texRIndex;
|
||||||
int texWIndex;
|
int texWIndex;
|
||||||
volatile int texReady;
|
atomic_int texReady;
|
||||||
bool needsRelease;
|
bool needsRelease;
|
||||||
|
|
||||||
CaptureGetPointerBuffer getPointerBufferFn;
|
CaptureGetPointerBuffer getPointerBufferFn;
|
||||||
@@ -202,8 +204,8 @@ static bool dxgi_init()
|
|||||||
{
|
{
|
||||||
DEBUG_INFO("The above error(s) will prevent LG from being able to capture the secure desktop (UAC dialogs)");
|
DEBUG_INFO("The above error(s) will prevent LG from being able to capture the secure desktop (UAC dialogs)");
|
||||||
DEBUG_INFO("This is not a failure, please do not report this as an issue.");
|
DEBUG_INFO("This is not a failure, please do not report this as an issue.");
|
||||||
DEBUG_INFO("To fix this run LG using the PsExec SysInternals tool from Microsoft.");
|
DEBUG_INFO("To fix this, install and run the Looking Glass host as a service.");
|
||||||
DEBUG_INFO("https://docs.microsoft.com/en-us/sysinternals/downloads/psexec");
|
DEBUG_INFO("looking-glass-host.exe InstallService");
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is required for DXGI 1.5 support to function
|
// this is required for DXGI 1.5 support to function
|
||||||
@@ -228,9 +230,9 @@ static bool dxgi_init()
|
|||||||
this->stop = false;
|
this->stop = false;
|
||||||
this->texRIndex = 0;
|
this->texRIndex = 0;
|
||||||
this->texWIndex = 0;
|
this->texWIndex = 0;
|
||||||
this->texReady = 0;
|
atomic_store(&this->texReady, 0);
|
||||||
|
|
||||||
lgResetEvent(this->frameEvent );
|
lgResetEvent(this->frameEvent);
|
||||||
|
|
||||||
status = CreateDXGIFactory1(&IID_IDXGIFactory1, (void **)&this->factory);
|
status = CreateDXGIFactory1(&IID_IDXGIFactory1, (void **)&this->factory);
|
||||||
if (FAILED(status))
|
if (FAILED(status))
|
||||||
@@ -408,9 +410,10 @@ static bool dxgi_init()
|
|||||||
status = fn(GetCurrentProcess(), D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME);
|
status = fn(GetCurrentProcess(), D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME);
|
||||||
if (FAILED(status))
|
if (FAILED(status))
|
||||||
{
|
{
|
||||||
DEBUG_INFO("Failed to set realtime GPU priority, this is not an error!");
|
DEBUG_WARN("Failed to set realtime GPU priority.");
|
||||||
DEBUG_INFO("To fix this run LG using the PsExec SysInternals tool from Microsoft.");
|
DEBUG_INFO("This is not a failure, please do not report this as an issue.");
|
||||||
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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -704,6 +707,15 @@ static CaptureResult dxgi_capture()
|
|||||||
DXGI_OUTDUPL_FRAME_INFO frameInfo;
|
DXGI_OUTDUPL_FRAME_INFO frameInfo;
|
||||||
IDXGIResource * res;
|
IDXGIResource * res;
|
||||||
|
|
||||||
|
bool copyFrame = false;
|
||||||
|
bool copyPointer = false;
|
||||||
|
ID3D11Texture2D * src;
|
||||||
|
|
||||||
|
bool postPointer = false;
|
||||||
|
CapturePointer pointer = { 0 };
|
||||||
|
void * pointerShape = NULL;
|
||||||
|
UINT pointerShapeSize = 0;
|
||||||
|
|
||||||
// release the prior frame
|
// release the prior frame
|
||||||
result = dxgi_releaseFrame();
|
result = dxgi_releaseFrame();
|
||||||
if (result != CAPTURE_RESULT_OK)
|
if (result != CAPTURE_RESULT_OK)
|
||||||
@@ -735,7 +747,7 @@ static CaptureResult dxgi_capture()
|
|||||||
// check if the texture is free, if not skip the frame to keep up
|
// check if the texture is free, if not skip the frame to keep up
|
||||||
if (tex->state == TEXTURE_STATE_UNUSED)
|
if (tex->state == TEXTURE_STATE_UNUSED)
|
||||||
{
|
{
|
||||||
ID3D11Texture2D * src;
|
copyFrame = true;
|
||||||
status = IDXGIResource_QueryInterface(res, &IID_ID3D11Texture2D, (void **)&src);
|
status = IDXGIResource_QueryInterface(res, &IID_ID3D11Texture2D, (void **)&src);
|
||||||
if (FAILED(status))
|
if (FAILED(status))
|
||||||
{
|
{
|
||||||
@@ -743,18 +755,50 @@ static CaptureResult dxgi_capture()
|
|||||||
IDXGIResource_Release(res);
|
IDXGIResource_Release(res);
|
||||||
return CAPTURE_RESULT_ERROR;
|
return CAPTURE_RESULT_ERROR;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LOCKED({
|
IDXGIResource_Release(res);
|
||||||
// issue the copy from GPU to CPU RAM and release the src
|
|
||||||
|
// if the pointer shape has changed
|
||||||
|
uint32_t bufferSize;
|
||||||
|
if (frameInfo.PointerShapeBufferSize > 0)
|
||||||
|
{
|
||||||
|
if(!this->getPointerBufferFn(&pointerShape, &bufferSize))
|
||||||
|
DEBUG_WARN("Failed to obtain a buffer for the pointer shape");
|
||||||
|
else
|
||||||
|
copyPointer = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (copyFrame || copyPointer)
|
||||||
|
{
|
||||||
|
DXGI_OUTDUPL_POINTER_SHAPE_INFO shapeInfo;
|
||||||
|
LOCKED(
|
||||||
|
{
|
||||||
|
if (copyFrame)
|
||||||
|
{
|
||||||
|
// issue the copy from GPU to CPU RAM
|
||||||
ID3D11DeviceContext_CopyResource(this->deviceContext,
|
ID3D11DeviceContext_CopyResource(this->deviceContext,
|
||||||
(ID3D11Resource *)tex->tex, (ID3D11Resource *)src);
|
(ID3D11Resource *)tex->tex, (ID3D11Resource *)src);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (copyPointer)
|
||||||
|
{
|
||||||
|
// grab the pointer shape
|
||||||
|
status = IDXGIOutputDuplication_GetFramePointerShape(
|
||||||
|
this->dup, bufferSize, pointerShape, &pointerShapeSize, &shapeInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
ID3D11DeviceContext_Flush(this->deviceContext);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (copyFrame)
|
||||||
|
{
|
||||||
ID3D11Texture2D_Release(src);
|
ID3D11Texture2D_Release(src);
|
||||||
|
|
||||||
// set the state, and signal
|
// set the state, and signal
|
||||||
tex->state = TEXTURE_STATE_PENDING_MAP;
|
tex->state = TEXTURE_STATE_PENDING_MAP;
|
||||||
INTERLOCKED_INC(&this->texReady);
|
if (atomic_fetch_add_explicit(&this->texReady, 1, memory_order_relaxed) == 0)
|
||||||
lgSignalEvent(this->frameEvent);
|
lgSignalEvent(this->frameEvent);
|
||||||
|
|
||||||
// advance the write index
|
// advance the write index
|
||||||
@@ -764,15 +808,62 @@ static CaptureResult dxgi_capture()
|
|||||||
// update the last frame time
|
// update the last frame time
|
||||||
this->frameTime.QuadPart = frameInfo.LastPresentTime.QuadPart;
|
this->frameTime.QuadPart = frameInfo.LastPresentTime.QuadPart;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (copyPointer)
|
||||||
|
{
|
||||||
|
result = dxgi_hResultToCaptureResult(status);
|
||||||
|
if (result != CAPTURE_RESULT_OK)
|
||||||
|
{
|
||||||
|
if (result == CAPTURE_RESULT_ERROR)
|
||||||
|
DEBUG_WINERROR("Failed to get the new pointer shape", status);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
IDXGIResource_Release(res);
|
switch(shapeInfo.Type)
|
||||||
|
{
|
||||||
|
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR : pointer.format = CAPTURE_FMT_COLOR ; break;
|
||||||
|
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: pointer.format = CAPTURE_FMT_MASKED; break;
|
||||||
|
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME : pointer.format = CAPTURE_FMT_MONO ; break;
|
||||||
|
default:
|
||||||
|
DEBUG_ERROR("Unsupported cursor format");
|
||||||
|
return CAPTURE_RESULT_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
// if the pointer has moved or changed state
|
CURSORINFO ci = { .cbSize = sizeof(CURSORINFO) };
|
||||||
bool postPointer = false;
|
if (!GetCursorInfo(&ci))
|
||||||
CapturePointer pointer = { 0 };
|
{
|
||||||
void * pointerShape = NULL;
|
DEBUG_WINERROR("GetCursorInfo failed", GetLastError());
|
||||||
UINT pointerShapeSize = 0;
|
return CAPTURE_RESULT_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ci.hCursor)
|
||||||
|
{
|
||||||
|
ICONINFO ii;
|
||||||
|
if (!GetIconInfo(ci.hCursor, &ii))
|
||||||
|
{
|
||||||
|
DEBUG_WINERROR("GetIconInfo failed", GetLastError());
|
||||||
|
return CAPTURE_RESULT_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeleteObject(ii.hbmMask);
|
||||||
|
DeleteObject(ii.hbmColor);
|
||||||
|
|
||||||
|
pointer.hx = ii.xHotspot;
|
||||||
|
pointer.hy = ii.yHotspot;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pointer.hx = 0;
|
||||||
|
pointer.hy = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pointer.shapeUpdate = true;
|
||||||
|
pointer.width = shapeInfo.Width;
|
||||||
|
pointer.height = shapeInfo.Height;
|
||||||
|
pointer.pitch = shapeInfo.Pitch;
|
||||||
|
postPointer = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (frameInfo.LastMouseUpdateTime.QuadPart)
|
if (frameInfo.LastMouseUpdateTime.QuadPart)
|
||||||
{
|
{
|
||||||
@@ -798,43 +889,6 @@ static CaptureResult dxgi_capture()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the pointer shape has changed
|
|
||||||
if (frameInfo.PointerShapeBufferSize > 0)
|
|
||||||
{
|
|
||||||
uint32_t bufferSize;
|
|
||||||
if(!this->getPointerBufferFn(&pointerShape, &bufferSize))
|
|
||||||
DEBUG_WARN("Failed to obtain a buffer for the pointer shape");
|
|
||||||
else
|
|
||||||
{
|
|
||||||
DXGI_OUTDUPL_POINTER_SHAPE_INFO shapeInfo;
|
|
||||||
|
|
||||||
LOCKED({status = IDXGIOutputDuplication_GetFramePointerShape(this->dup, bufferSize, pointerShape, &pointerShapeSize, &shapeInfo);});
|
|
||||||
result = dxgi_hResultToCaptureResult(status);
|
|
||||||
if (result != CAPTURE_RESULT_OK)
|
|
||||||
{
|
|
||||||
if (result == CAPTURE_RESULT_ERROR)
|
|
||||||
DEBUG_WINERROR("Failed to get the new pointer shape", status);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(shapeInfo.Type)
|
|
||||||
{
|
|
||||||
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR : pointer.format = CAPTURE_FMT_COLOR ; break;
|
|
||||||
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: pointer.format = CAPTURE_FMT_MASKED; break;
|
|
||||||
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME : pointer.format = CAPTURE_FMT_MONO ; break;
|
|
||||||
default:
|
|
||||||
DEBUG_ERROR("Unsupported cursor format");
|
|
||||||
return CAPTURE_RESULT_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
pointer.shapeUpdate = true;
|
|
||||||
pointer.width = shapeInfo.Width;
|
|
||||||
pointer.height = shapeInfo.Height;
|
|
||||||
pointer.pitch = shapeInfo.Pitch;
|
|
||||||
postPointer = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// post back the pointer information
|
// post back the pointer information
|
||||||
if (postPointer)
|
if (postPointer)
|
||||||
{
|
{
|
||||||
@@ -851,29 +905,41 @@ static CaptureResult dxgi_waitFrame(CaptureFrame * frame)
|
|||||||
assert(this->initialized);
|
assert(this->initialized);
|
||||||
|
|
||||||
// NOTE: the event may be signaled when there are no frames available
|
// NOTE: the event may be signaled when there are no frames available
|
||||||
if(this->texReady == 0)
|
if(atomic_load_explicit(&this->texReady, memory_order_acquire) == 0)
|
||||||
{
|
{
|
||||||
if (!lgWaitEvent(this->frameEvent, 1000))
|
if (!lgWaitEvent(this->frameEvent, 1000))
|
||||||
return CAPTURE_RESULT_TIMEOUT;
|
return CAPTURE_RESULT_TIMEOUT;
|
||||||
|
|
||||||
if (this->texReady == 0)
|
// the count will still be zero if we are stopping
|
||||||
|
if(atomic_load_explicit(&this->texReady, memory_order_acquire) == 0)
|
||||||
return CAPTURE_RESULT_TIMEOUT;
|
return CAPTURE_RESULT_TIMEOUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
Texture * tex = &this->texture[this->texRIndex];
|
Texture * tex = &this->texture[this->texRIndex];
|
||||||
|
|
||||||
// try to map the resource, but don't wait for it
|
// try to map the resource, but don't wait for it
|
||||||
|
for (int i = 0; ; ++i)
|
||||||
|
{
|
||||||
HRESULT status;
|
HRESULT status;
|
||||||
LOCKED({status = ID3D11DeviceContext_Map(this->deviceContext, (ID3D11Resource*)tex->tex, 0, D3D11_MAP_READ, 0x100000L, &tex->map);});
|
LOCKED({status = ID3D11DeviceContext_Map(this->deviceContext, (ID3D11Resource*)tex->tex, 0, D3D11_MAP_READ, 0x100000L, &tex->map);});
|
||||||
if (status == DXGI_ERROR_WAS_STILL_DRAWING)
|
if (status == DXGI_ERROR_WAS_STILL_DRAWING)
|
||||||
|
{
|
||||||
|
if (i == 100)
|
||||||
return CAPTURE_RESULT_TIMEOUT;
|
return CAPTURE_RESULT_TIMEOUT;
|
||||||
|
|
||||||
|
usleep(1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (FAILED(status))
|
if (FAILED(status))
|
||||||
{
|
{
|
||||||
DEBUG_WINERROR("Failed to map the texture", status);
|
DEBUG_WINERROR("Failed to map the texture", status);
|
||||||
return CAPTURE_RESULT_ERROR;
|
return CAPTURE_RESULT_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
tex->state = TEXTURE_STATE_MAPPED;
|
tex->state = TEXTURE_STATE_MAPPED;
|
||||||
|
|
||||||
frame->width = this->width;
|
frame->width = this->width;
|
||||||
@@ -882,7 +948,7 @@ static CaptureResult dxgi_waitFrame(CaptureFrame * frame)
|
|||||||
frame->stride = this->stride;
|
frame->stride = this->stride;
|
||||||
frame->format = this->format;
|
frame->format = this->format;
|
||||||
|
|
||||||
INTERLOCKED_DEC(&this->texReady);
|
atomic_fetch_sub_explicit(&this->texReady, 1, memory_order_release);
|
||||||
return CAPTURE_RESULT_OK;
|
return CAPTURE_RESULT_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -352,8 +352,8 @@ static int pointerThread(void * unused)
|
|||||||
}
|
}
|
||||||
|
|
||||||
this->mouseVisible = pointer.visible;
|
this->mouseVisible = pointer.visible;
|
||||||
this->mouseHotX = pointer.x;
|
this->mouseHotX = pointer.hx;
|
||||||
this->mouseHotY = pointer.y;
|
this->mouseHotY = pointer.hy;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (events[0])
|
if (events[0])
|
||||||
|
|||||||
@@ -288,8 +288,8 @@ CaptureResult NvFBCToSysGetCursor(NvFBCHandle handle, CapturePointer * pointer,
|
|||||||
return CAPTURE_RESULT_ERROR;
|
return CAPTURE_RESULT_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
pointer->x = params.dwXHotSpot;
|
pointer->hx = params.dwXHotSpot;
|
||||||
pointer->y = params.dwYHotSpot;
|
pointer->hy = params.dwYHotSpot;
|
||||||
pointer->width = params.dwWidth;
|
pointer->width = params.dwWidth;
|
||||||
pointer->height = params.dwHeight;
|
pointer->height = params.dwHeight;
|
||||||
pointer->pitch = params.dwPitch;
|
pointer->pitch = params.dwPitch;
|
||||||
|
|||||||
198
host/platform/Windows/installer.nsi
Normal file
198
host/platform/Windows/installer.nsi
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
/*
|
||||||
|
Looking Glass - KVM FrameRelay (KVMFR) Client
|
||||||
|
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
|
||||||
|
https://looking-glass.hostfission.com
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify it under
|
||||||
|
the terms of the GNU General Public License as published by the Free Software
|
||||||
|
Foundation; either version 2 of the License, or (at your option) any later
|
||||||
|
version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along with
|
||||||
|
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||||
|
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
;Include
|
||||||
|
!include "MUI2.nsh"
|
||||||
|
!include "FileFunc.nsh"
|
||||||
|
!include "LogicLib.nsh"
|
||||||
|
!include "Sections.nsh"
|
||||||
|
|
||||||
|
;Settings
|
||||||
|
Name "Looking Glass (host)"
|
||||||
|
OutFile "looking-glass-host-setup.exe"
|
||||||
|
Unicode true
|
||||||
|
RequestExecutionLevel admin
|
||||||
|
ShowInstDetails "show"
|
||||||
|
ShowUninstDetails "show"
|
||||||
|
InstallDir "$PROGRAMFILES64\Looking Glass (host)"
|
||||||
|
|
||||||
|
!define MUI_ICON "icon.ico"
|
||||||
|
!define MUI_UNICON "icon.ico"
|
||||||
|
!define MUI_LICENSEPAGE_BUTTON "Agree"
|
||||||
|
!define /file VERSION "VERSION"
|
||||||
|
|
||||||
|
;Install and uninstall pages
|
||||||
|
!insertmacro MUI_PAGE_LICENSE "LICENSE.txt"
|
||||||
|
!insertmacro MUI_PAGE_DIRECTORY
|
||||||
|
!insertmacro MUI_PAGE_COMPONENTS
|
||||||
|
!insertmacro MUI_PAGE_INSTFILES
|
||||||
|
!insertmacro MUI_UNPAGE_CONFIRM
|
||||||
|
!insertmacro MUI_UNPAGE_INSTFILES
|
||||||
|
!insertmacro MUI_LANGUAGE "English"
|
||||||
|
|
||||||
|
|
||||||
|
Function ShowHelpMessage
|
||||||
|
!define line1 "Command line options:$\r$\n$\r$\n"
|
||||||
|
!define line2 "/S - silent install (must be uppercase)$\r$\n"
|
||||||
|
!define line3 "/D=path\to\install\folder - Change install directory$\r$\n"
|
||||||
|
!define line4 " (Must be uppercase, the last option given and no quotes)$\r$\n$\r$\n"
|
||||||
|
!define line5 "/startmenu - create start menu shortcut$\r$\n"
|
||||||
|
!define line6 "/desktop - create desktop shortcut$\r$\n"
|
||||||
|
!define line7 "/noservice - do not create a service to auto start and elevate the host"
|
||||||
|
MessageBox MB_OK "${line1}${line2}${line3}${line4}${line5}${line6}${line7}"
|
||||||
|
Abort
|
||||||
|
FunctionEnd
|
||||||
|
|
||||||
|
Function .onInit
|
||||||
|
|
||||||
|
var /GLOBAL cmdLineParams
|
||||||
|
Push $R0
|
||||||
|
${GetParameters} $cmdLineParams
|
||||||
|
ClearErrors
|
||||||
|
|
||||||
|
${GetOptions} $cmdLineParams '/?' $R0
|
||||||
|
IfErrors +2 0
|
||||||
|
Call ShowHelpMessage
|
||||||
|
|
||||||
|
${GetOptions} $cmdLineParams '/H' $R0
|
||||||
|
IfErrors +2 0
|
||||||
|
Call ShowHelpMessage
|
||||||
|
|
||||||
|
Pop $R0
|
||||||
|
|
||||||
|
|
||||||
|
Var /GLOBAL option_startMenu
|
||||||
|
Var /GLOBAL option_desktop
|
||||||
|
Var /GlOBAL option_noservice
|
||||||
|
StrCpy $option_startMenu 0
|
||||||
|
StrCpy $option_desktop 0
|
||||||
|
StrCpy $option_noservice 0
|
||||||
|
|
||||||
|
Push $R0
|
||||||
|
|
||||||
|
${GetOptions} $cmdLineParams '/startmenu' $R0
|
||||||
|
IfErrors +2 0
|
||||||
|
StrCpy $option_startMenu 1
|
||||||
|
|
||||||
|
${GetOptions} $cmdLineParams '/desktop' $R0
|
||||||
|
IfErrors +2 0
|
||||||
|
StrCpy $option_desktop 1
|
||||||
|
|
||||||
|
${GetOptions} $cmdLineParams '/noservice' $R0
|
||||||
|
IfErrors +2 0
|
||||||
|
StrCpy $option_noservice 1
|
||||||
|
|
||||||
|
Pop $R0
|
||||||
|
|
||||||
|
FunctionEnd
|
||||||
|
|
||||||
|
;Install
|
||||||
|
Section "-Install" Section1
|
||||||
|
|
||||||
|
nsExec::Exec 'net.exe STOP "Looking Glass (host)"'
|
||||||
|
|
||||||
|
SetOutPath $INSTDIR
|
||||||
|
File ..\..\looking-glass-host.exe
|
||||||
|
File LICENSE.txt
|
||||||
|
WriteUninstaller $INSTDIR\uninstaller.exe
|
||||||
|
|
||||||
|
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
|
||||||
|
IntFmt $0 "0x%08X" $0
|
||||||
|
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)" \
|
||||||
|
"EstimatedSize" "$0"
|
||||||
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)" \
|
||||||
|
"DisplayName" "Looking Glass (host)"
|
||||||
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)" \
|
||||||
|
"UninstallString" "$\"$INSTDIR\uninstaller.exe$\""
|
||||||
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)" \
|
||||||
|
"QuietUninstallString" "$\"$INSTDIR\uninstaller.exe$\" /S"
|
||||||
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)" \
|
||||||
|
"InstallLocation" "$INSTDIR"
|
||||||
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)" \
|
||||||
|
"Publisher" "Geoffrey McRae"
|
||||||
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)" \
|
||||||
|
"DisplayIcon" "$\"$INSTDIR\looking-glass-host.exe$\""
|
||||||
|
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)" \
|
||||||
|
"NoRepair" "1"
|
||||||
|
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)" \
|
||||||
|
"NoModify" "1"
|
||||||
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)" \
|
||||||
|
"DisplayVersion" ${VERSION}
|
||||||
|
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
Section "Looking Glass (host) Service" Section2
|
||||||
|
|
||||||
|
${If} $option_noservice == 0
|
||||||
|
nsExec::Exec '"$INSTDIR\looking-glass-host.exe" UninstallService'
|
||||||
|
nsExec::Exec '"$INSTDIR\looking-glass-host.exe" InstallService'
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
Section /o "Desktop Shortcut" Section3
|
||||||
|
StrCpy $option_desktop 1
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
Section /o "Start Menu Shortcut" Section4
|
||||||
|
StrCpy $option_startMenu 1
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
Section "-Hidden Start Menu" Section5
|
||||||
|
SetShellVarContext all
|
||||||
|
|
||||||
|
${If} $option_startMenu == 1
|
||||||
|
CreateShortCut "$SMPROGRAMS\Looking Glass (host).lnk" $INSTDIR\looking-glass-host.exe
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
${If} $option_desktop == 1
|
||||||
|
CreateShortCut "$DESKTOP\Looking Glass (host).lnk" $INSTDIR\looking-glass-host.exe
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
Section "Uninstall" Section6
|
||||||
|
SetShellVarContext all
|
||||||
|
|
||||||
|
nsExec::Exec 'net.exe STOP "Looking Glass (host)"'
|
||||||
|
nsExec::Exec '"$INSTDIR\looking-glass-host.exe" UninstallService'
|
||||||
|
|
||||||
|
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Looking Glass (host)"
|
||||||
|
Delete "$SMPROGRAMS\Looking Glass (host).lnk"
|
||||||
|
Delete "$DESKTOP\Looking Glass (host).lnk"
|
||||||
|
Delete "$INSTDIR\uninstaller.exe"
|
||||||
|
Delete "$INSTDIR\looking-glass-host.exe"
|
||||||
|
Delete "$INSTDIR\LICENSE.txt"
|
||||||
|
|
||||||
|
RMDir $INSTDIR
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
;Description text for selection of install items
|
||||||
|
LangString DESC_Section1 ${LANG_ENGLISH} "Install Files into $INSTDIR"
|
||||||
|
LangString DESC_Section2 ${LANG_ENGLISH} "Install service to automatically start Looking Glass (host)."
|
||||||
|
LangString DESC_Section3 ${LANG_ENGLISH} "Create desktop shortcut icon."
|
||||||
|
LangString DESC_Section4 ${LANG_ENGLISH} "Create start menu shortcut."
|
||||||
|
|
||||||
|
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
|
||||||
|
!insertmacro MUI_DESCRIPTION_TEXT ${Section1} $(DESC_Section1)
|
||||||
|
!insertmacro MUI_DESCRIPTION_TEXT ${Section2} $(DESC_Section2)
|
||||||
|
!insertmacro MUI_DESCRIPTION_TEXT ${Section3} $(DESC_Section3)
|
||||||
|
!insertmacro MUI_DESCRIPTION_TEXT ${Section4} $(DESC_Section4)
|
||||||
|
!insertmacro MUI_FUNCTION_DESCRIPTION_END
|
||||||
@@ -18,6 +18,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
|
#include "service.h"
|
||||||
#include "windows/mousehook.h"
|
#include "windows/mousehook.h"
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
@@ -31,7 +32,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
|
|||||||
#include "common/locking.h"
|
#include "common/locking.h"
|
||||||
#include "common/thread.h"
|
#include "common/thread.h"
|
||||||
|
|
||||||
#define ID_MENU_OPEN_LOG 3000
|
#define ID_MENU_SHOW_LOG 3000
|
||||||
#define ID_MENU_EXIT 3001
|
#define ID_MENU_EXIT 3001
|
||||||
|
|
||||||
struct AppState
|
struct AppState
|
||||||
@@ -44,6 +45,8 @@ struct AppState
|
|||||||
|
|
||||||
char executable[MAX_PATH + 1];
|
char executable[MAX_PATH + 1];
|
||||||
HWND messageWnd;
|
HWND messageWnd;
|
||||||
|
NOTIFYICONDATA iconData;
|
||||||
|
UINT trayRestartMsg;
|
||||||
HMENU trayMenu;
|
HMENU trayMenu;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -54,6 +57,30 @@ HWND MessageHWND;
|
|||||||
typedef NTSTATUS (__stdcall *ZwSetTimerResolution_t)(ULONG RequestedResolution, BOOLEAN Set, PULONG ActualResolution);
|
typedef NTSTATUS (__stdcall *ZwSetTimerResolution_t)(ULONG RequestedResolution, BOOLEAN Set, PULONG ActualResolution);
|
||||||
static ZwSetTimerResolution_t ZwSetTimerResolution = NULL;
|
static ZwSetTimerResolution_t ZwSetTimerResolution = NULL;
|
||||||
|
|
||||||
|
// linux mingw64 is missing this
|
||||||
|
#ifndef MSGFLT_RESET
|
||||||
|
#define MSGFLT_RESET (0)
|
||||||
|
#define MSGFLT_ALLOW (1)
|
||||||
|
#define MSGFLT_DISALLOW (2)
|
||||||
|
#endif
|
||||||
|
typedef WINBOOL WINAPI (*PChangeWindowMessageFilterEx)(HWND hwnd, UINT message, DWORD action, void * pChangeFilterStruct);
|
||||||
|
PChangeWindowMessageFilterEx _ChangeWindowMessageFilterEx = NULL;
|
||||||
|
|
||||||
|
static void RegisterTrayIcon()
|
||||||
|
{
|
||||||
|
// register our TrayIcon
|
||||||
|
if (!app.iconData.cbSize)
|
||||||
|
{
|
||||||
|
app.iconData.cbSize = sizeof(NOTIFYICONDATA);
|
||||||
|
app.iconData.hWnd = app.messageWnd;
|
||||||
|
app.iconData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
|
||||||
|
app.iconData.uCallbackMessage = WM_TRAYICON;
|
||||||
|
strncpy(app.iconData.szTip, "Looking Glass (host)", sizeof(app.iconData.szTip));
|
||||||
|
app.iconData.hIcon = LoadIcon(app.hInst, IDI_APPLICATION);
|
||||||
|
}
|
||||||
|
Shell_NotifyIcon(NIM_ADD, &app.iconData);
|
||||||
|
}
|
||||||
|
|
||||||
LRESULT CALLBACK DummyWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
LRESULT CALLBACK DummyWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
||||||
{
|
{
|
||||||
switch(msg)
|
switch(msg)
|
||||||
@@ -86,41 +113,39 @@ LRESULT CALLBACK DummyWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (clicked == ID_MENU_EXIT ) app_quit();
|
if (clicked == ID_MENU_EXIT ) app_quit();
|
||||||
else if (clicked == ID_MENU_OPEN_LOG)
|
else if (clicked == ID_MENU_SHOW_LOG)
|
||||||
{
|
{
|
||||||
const char * logFile = option_get_string("os", "logFile");
|
const char * logFile = option_get_string("os", "logFile");
|
||||||
if (strcmp(logFile, "stderr") == 0)
|
if (strcmp(logFile, "stderr") == 0)
|
||||||
DEBUG_INFO("Ignoring request to open the logFile, logging to stderr");
|
DEBUG_INFO("Ignoring request to open the logFile, logging to stderr");
|
||||||
else
|
else
|
||||||
ShellExecute(NULL, NULL, logFile, NULL, NULL, SW_SHOWNORMAL);
|
{
|
||||||
|
/* If LG is running as SYSTEM, ShellExecute would launch a process
|
||||||
|
* as the SYSTEM user also, for security we will just show the file
|
||||||
|
* location instead */
|
||||||
|
//ShellExecute(NULL, NULL, logFile, NULL, NULL, SW_SHOWNORMAL);
|
||||||
|
MessageBoxA(hwnd, logFile, "Log File Location", MB_OK | MB_ICONINFORMATION);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return DefWindowProc(hwnd, msg, wParam, lParam);
|
if (msg == app.trayRestartMsg)
|
||||||
|
RegisterTrayIcon();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return 0;
|
|
||||||
|
return DefWindowProc(hwnd, msg, wParam, lParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int appThread(void * opaque)
|
static int appThread(void * opaque)
|
||||||
{
|
{
|
||||||
// register our TrayIcon
|
RegisterTrayIcon();
|
||||||
NOTIFYICONDATA iconData =
|
|
||||||
{
|
|
||||||
.cbSize = sizeof(NOTIFYICONDATA),
|
|
||||||
.hWnd = app.messageWnd,
|
|
||||||
.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP,
|
|
||||||
.uCallbackMessage = WM_TRAYICON,
|
|
||||||
.szTip = "Looking Glass (host)"
|
|
||||||
};
|
|
||||||
iconData.hIcon = LoadIcon(app.hInst, IDI_APPLICATION);
|
|
||||||
Shell_NotifyIcon(NIM_ADD, &iconData);
|
|
||||||
|
|
||||||
int result = app_main(app.argc, app.argv);
|
int result = app_main(app.argc, app.argv);
|
||||||
|
|
||||||
Shell_NotifyIcon(NIM_DELETE, &iconData);
|
Shell_NotifyIcon(NIM_DELETE, &app.iconData);
|
||||||
mouseHook_remove();
|
mouseHook_remove();
|
||||||
SendMessage(app.messageWnd, WM_DESTROY, 0, 0);
|
SendMessage(app.messageWnd, WM_DESTROY, 0, 0);
|
||||||
return result;
|
return result;
|
||||||
@@ -144,6 +169,21 @@ static BOOL WINAPI CtrlHandler(DWORD dwCtrlType)
|
|||||||
|
|
||||||
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
|
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
|
||||||
{
|
{
|
||||||
|
// convert the command line to the standard argc and argv
|
||||||
|
LPWSTR * wargv = CommandLineToArgvW(GetCommandLineW(), &app.argc);
|
||||||
|
app.argv = malloc(sizeof(char *) * app.argc);
|
||||||
|
for(int i = 0; i < app.argc; ++i)
|
||||||
|
{
|
||||||
|
const size_t s = (wcslen(wargv[i])+1) * 2;
|
||||||
|
app.argv[i] = malloc(s);
|
||||||
|
wcstombs(app.argv[i], wargv[i], s);
|
||||||
|
}
|
||||||
|
LocalFree(wargv);
|
||||||
|
|
||||||
|
GetModuleFileName(NULL, app.executable, sizeof(app.executable));
|
||||||
|
if (HandleService(app.argc, app.argv))
|
||||||
|
return 0;
|
||||||
|
|
||||||
/* this is a bit of a hack but without this --help will produce no output in a windows command prompt */
|
/* this is a bit of a hack but without this --help will produce no output in a windows command prompt */
|
||||||
if (!IsDebuggerPresent() && AttachConsole(ATTACH_PARENT_PROCESS))
|
if (!IsDebuggerPresent() && AttachConsole(ATTACH_PARENT_PROCESS))
|
||||||
{
|
{
|
||||||
@@ -183,19 +223,6 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
|||||||
option_register(options);
|
option_register(options);
|
||||||
free(logFilePath);
|
free(logFilePath);
|
||||||
|
|
||||||
// convert the command line to the standard argc and argv
|
|
||||||
LPWSTR * wargv = CommandLineToArgvW(GetCommandLineW(), &app.argc);
|
|
||||||
app.argv = malloc(sizeof(char *) * app.argc);
|
|
||||||
for(int i = 0; i < app.argc; ++i)
|
|
||||||
{
|
|
||||||
const size_t s = (wcslen(wargv[i])+1) * 2;
|
|
||||||
app.argv[i] = malloc(s);
|
|
||||||
wcstombs(app.argv[i], wargv[i], s);
|
|
||||||
}
|
|
||||||
LocalFree(wargv);
|
|
||||||
|
|
||||||
GetModuleFileName(NULL, app.executable, sizeof(app.executable));
|
|
||||||
|
|
||||||
// setup a handler for ctrl+c
|
// setup a handler for ctrl+c
|
||||||
SetConsoleCtrlHandler(CtrlHandler, TRUE);
|
SetConsoleCtrlHandler(CtrlHandler, TRUE);
|
||||||
|
|
||||||
@@ -209,19 +236,29 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
|||||||
wx.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
|
wx.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
|
||||||
wx.hCursor = LoadCursor(NULL, IDC_ARROW);
|
wx.hCursor = LoadCursor(NULL, IDC_ARROW);
|
||||||
wx.hbrBackground = (HBRUSH)COLOR_APPWORKSPACE;
|
wx.hbrBackground = (HBRUSH)COLOR_APPWORKSPACE;
|
||||||
if (!RegisterClassEx(&wx))
|
ATOM class;
|
||||||
|
if (!(class = RegisterClassEx(&wx)))
|
||||||
{
|
{
|
||||||
DEBUG_ERROR("Failed to register message window class");
|
DEBUG_ERROR("Failed to register message window class");
|
||||||
result = -1;
|
result = -1;
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
app.messageWnd = CreateWindowEx(0, "DUMMY_CLASS", "DUMMY_NAME", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
|
|
||||||
|
app.trayRestartMsg = RegisterWindowMessage("TaskbarCreated");
|
||||||
|
|
||||||
|
app.messageWnd = CreateWindowEx(0, MAKEINTATOM(class), NULL, 0, 0, 0, 0, 0, NULL, NULL, hInstance, NULL);
|
||||||
|
|
||||||
|
// this is needed so that unprivileged processes can send us this message
|
||||||
|
HMODULE user32 = GetModuleHandle("user32.dll");
|
||||||
|
_ChangeWindowMessageFilterEx = (PChangeWindowMessageFilterEx)GetProcAddress(user32, "ChangeWindowMessageFilterEx");
|
||||||
|
if (_ChangeWindowMessageFilterEx)
|
||||||
|
_ChangeWindowMessageFilterEx(app.messageWnd, app.trayRestartMsg, MSGFLT_ALLOW, NULL);
|
||||||
|
|
||||||
// set the global
|
// set the global
|
||||||
MessageHWND = app.messageWnd;
|
MessageHWND = app.messageWnd;
|
||||||
|
|
||||||
app.trayMenu = CreatePopupMenu();
|
app.trayMenu = CreatePopupMenu();
|
||||||
AppendMenu(app.trayMenu, MF_STRING , ID_MENU_OPEN_LOG, "Open Log File");
|
AppendMenu(app.trayMenu, MF_STRING , ID_MENU_SHOW_LOG, "Log File Location");
|
||||||
AppendMenu(app.trayMenu, MF_SEPARATOR, 0 , NULL );
|
AppendMenu(app.trayMenu, MF_SEPARATOR, 0 , NULL );
|
||||||
AppendMenu(app.trayMenu, MF_STRING , ID_MENU_EXIT , "Exit" );
|
AppendMenu(app.trayMenu, MF_STRING , ID_MENU_EXIT , "Exit" );
|
||||||
|
|
||||||
|
|||||||
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[]);
|
||||||
@@ -62,6 +62,14 @@ static const struct LGMPQueueConfig POINTER_QUEUE_CONFIG =
|
|||||||
|
|
||||||
#define MAX_POINTER_SIZE (sizeof(KVMFRCursor) + (128 * 128 * 4))
|
#define MAX_POINTER_SIZE (sizeof(KVMFRCursor) + (128 * 128 * 4))
|
||||||
|
|
||||||
|
enum AppState
|
||||||
|
{
|
||||||
|
APP_STATE_RUNNING,
|
||||||
|
APP_STATE_IDLE,
|
||||||
|
APP_STATE_RESTART,
|
||||||
|
APP_STATE_SHUTDOWN
|
||||||
|
};
|
||||||
|
|
||||||
struct app
|
struct app
|
||||||
{
|
{
|
||||||
PLGMPHost lgmp;
|
PLGMPHost lgmp;
|
||||||
@@ -81,8 +89,7 @@ struct app
|
|||||||
|
|
||||||
CaptureInterface * iface;
|
CaptureInterface * iface;
|
||||||
|
|
||||||
bool running;
|
enum AppState state;
|
||||||
bool reinit;
|
|
||||||
LGTimer * lgmpTimer;
|
LGTimer * lgmpTimer;
|
||||||
LGThread * frameThread;
|
LGThread * frameThread;
|
||||||
};
|
};
|
||||||
@@ -95,7 +102,7 @@ static bool lgmpTimer(void * opaque)
|
|||||||
if ((status = lgmpHostProcess(app.lgmp)) != LGMP_OK)
|
if ((status = lgmpHostProcess(app.lgmp)) != LGMP_OK)
|
||||||
{
|
{
|
||||||
DEBUG_ERROR("lgmpHostProcess Failed: %s", lgmpStatusString(status));
|
DEBUG_ERROR("lgmpHostProcess Failed: %s", lgmpStatusString(status));
|
||||||
app.running = false;
|
app.state = APP_STATE_SHUTDOWN;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +118,7 @@ static int frameThread(void * opaque)
|
|||||||
CaptureFrame frame = { 0 };
|
CaptureFrame frame = { 0 };
|
||||||
const long pageSize = sysinfo_getPageSize();
|
const long pageSize = sysinfo_getPageSize();
|
||||||
|
|
||||||
while(app.running)
|
while(app.state == APP_STATE_RUNNING)
|
||||||
{
|
{
|
||||||
//wait until there is room in the queue
|
//wait until there is room in the queue
|
||||||
if(lgmpHostQueuePending(app.frameQueue) == LGMP_Q_FRAME_LEN)
|
if(lgmpHostQueuePending(app.frameQueue) == LGMP_Q_FRAME_LEN)
|
||||||
@@ -128,7 +135,7 @@ static int frameThread(void * opaque)
|
|||||||
|
|
||||||
case CAPTURE_RESULT_REINIT:
|
case CAPTURE_RESULT_REINIT:
|
||||||
{
|
{
|
||||||
app.reinit = true;
|
app.state = APP_STATE_RESTART;
|
||||||
DEBUG_INFO("Frame thread reinit");
|
DEBUG_INFO("Frame thread reinit");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -205,7 +212,7 @@ static int frameThread(void * opaque)
|
|||||||
|
|
||||||
bool startThreads()
|
bool startThreads()
|
||||||
{
|
{
|
||||||
app.running = true;
|
app.state = APP_STATE_RUNNING;
|
||||||
if (!lgCreateThread("FrameThread", frameThread, NULL, &app.frameThread))
|
if (!lgCreateThread("FrameThread", frameThread, NULL, &app.frameThread))
|
||||||
{
|
{
|
||||||
DEBUG_ERROR("Failed to create the frame thread");
|
DEBUG_ERROR("Failed to create the frame thread");
|
||||||
@@ -219,8 +226,9 @@ bool stopThreads()
|
|||||||
{
|
{
|
||||||
bool ok = true;
|
bool ok = true;
|
||||||
|
|
||||||
app.running = false;
|
|
||||||
app.iface->stop();
|
app.iface->stop();
|
||||||
|
if (app.state != APP_STATE_SHUTDOWN)
|
||||||
|
app.state = APP_STATE_IDLE;
|
||||||
|
|
||||||
if (app.frameThread && !lgJoinThread(app.frameThread, NULL))
|
if (app.frameThread && !lgJoinThread(app.frameThread, NULL))
|
||||||
{
|
{
|
||||||
@@ -234,7 +242,14 @@ bool stopThreads()
|
|||||||
|
|
||||||
static bool captureStart()
|
static bool captureStart()
|
||||||
{
|
{
|
||||||
DEBUG_INFO("Using : %s", app.iface->getName());
|
if (app.state == APP_STATE_IDLE)
|
||||||
|
{
|
||||||
|
if (!app.iface->init())
|
||||||
|
{
|
||||||
|
DEBUG_ERROR("Initialize the capture device");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const unsigned int maxFrameSize = app.iface->getMaxFrameSize();
|
const unsigned int maxFrameSize = app.iface->getMaxFrameSize();
|
||||||
if (maxFrameSize > app.maxFrameSize)
|
if (maxFrameSize > app.maxFrameSize)
|
||||||
@@ -248,31 +263,33 @@ static bool captureStart()
|
|||||||
return startThreads();
|
return startThreads();
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool captureRestart()
|
static bool captureStop()
|
||||||
{
|
{
|
||||||
DEBUG_INFO("==== [ Capture Restart ] ====");
|
DEBUG_INFO("==== [ Capture Stop ] ====");
|
||||||
if (!stopThreads())
|
if (!stopThreads())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!app.iface->deinit() || !app.iface->init())
|
if (!app.iface->deinit())
|
||||||
{
|
{
|
||||||
DEBUG_ERROR("Failed to reinitialize the capture device");
|
DEBUG_ERROR("Failed to deinitialize the capture device");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!captureStart())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool captureRestart()
|
||||||
|
{
|
||||||
|
return captureStop() && captureStart();
|
||||||
|
}
|
||||||
|
|
||||||
bool captureGetPointerBuffer(void ** data, uint32_t * size)
|
bool captureGetPointerBuffer(void ** data, uint32_t * size)
|
||||||
{
|
{
|
||||||
// spin until there is room
|
// spin until there is room
|
||||||
while(lgmpHostQueuePending(app.pointerQueue) == LGMP_Q_POINTER_LEN)
|
while(lgmpHostQueuePending(app.pointerQueue) == LGMP_Q_POINTER_LEN)
|
||||||
{
|
{
|
||||||
usleep(1);
|
usleep(1);
|
||||||
if (!app.running)
|
if (app.state == APP_STATE_RUNNING)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,10 +302,9 @@ bool captureGetPointerBuffer(void ** data, uint32_t * size)
|
|||||||
static void sendPointer(bool newClient)
|
static void sendPointer(bool newClient)
|
||||||
{
|
{
|
||||||
PLGMPMemory mem;
|
PLGMPMemory mem;
|
||||||
|
|
||||||
if (app.pointerInfo.shapeUpdate || newClient)
|
if (app.pointerInfo.shapeUpdate || newClient)
|
||||||
{
|
{
|
||||||
if (app.pointerInfo.shapeUpdate)
|
if (!newClient)
|
||||||
{
|
{
|
||||||
// swap the latest shape buffer out of rotation
|
// swap the latest shape buffer out of rotation
|
||||||
PLGMPMemory tmp = app.pointerShape;
|
PLGMPMemory tmp = app.pointerShape;
|
||||||
@@ -320,7 +336,8 @@ static void sendPointer(bool newClient)
|
|||||||
|
|
||||||
if (app.pointerInfo.shapeUpdate)
|
if (app.pointerInfo.shapeUpdate)
|
||||||
{
|
{
|
||||||
// remember which slot has the latest shape
|
cursor->hx = app.pointerInfo.hx;
|
||||||
|
cursor->hy = app.pointerInfo.hy;
|
||||||
cursor->width = app.pointerInfo.width;
|
cursor->width = app.pointerInfo.width;
|
||||||
cursor->height = app.pointerInfo.height;
|
cursor->height = app.pointerInfo.height;
|
||||||
cursor->pitch = app.pointerInfo.pitch;
|
cursor->pitch = app.pointerInfo.pitch;
|
||||||
@@ -473,6 +490,7 @@ int app_main(int argc, char * argv[])
|
|||||||
DEBUG_ERROR("lgmpHostMemAlloc Failed (Pointer): %s", lgmpStatusString(status));
|
DEBUG_ERROR("lgmpHostMemAlloc Failed (Pointer): %s", lgmpStatusString(status));
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
memset(lgmpHostMemPtr(app.pointerMemory[i]), 0, MAX_POINTER_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
app.pointerShapeValid = false;
|
app.pointerShapeValid = false;
|
||||||
@@ -523,6 +541,9 @@ int app_main(int argc, char * argv[])
|
|||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DEBUG_INFO("Using : %s", iface->getName());
|
||||||
|
|
||||||
|
app.state = APP_STATE_RUNNING;
|
||||||
app.iface = iface;
|
app.iface = iface;
|
||||||
|
|
||||||
LG_LOCK_INIT(app.pointerLock);
|
LG_LOCK_INIT(app.pointerLock);
|
||||||
@@ -533,20 +554,36 @@ int app_main(int argc, char * argv[])
|
|||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while(app.state != APP_STATE_SHUTDOWN)
|
||||||
|
{
|
||||||
|
if(lgmpHostQueueHasSubs(app.pointerQueue) ||
|
||||||
|
lgmpHostQueueHasSubs(app.frameQueue))
|
||||||
|
{
|
||||||
if (!captureStart())
|
if (!captureStart())
|
||||||
{
|
{
|
||||||
exitcode = -1;
|
exitcode = -1;
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
while(app.running)
|
else
|
||||||
{
|
{
|
||||||
if (app.reinit && !captureRestart())
|
usleep(100000);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
while(app.state != APP_STATE_SHUTDOWN && (
|
||||||
|
lgmpHostQueueHasSubs(app.pointerQueue) ||
|
||||||
|
lgmpHostQueueHasSubs(app.frameQueue)))
|
||||||
|
{
|
||||||
|
if (app.state == APP_STATE_RESTART)
|
||||||
|
{
|
||||||
|
if (!captureRestart())
|
||||||
{
|
{
|
||||||
exitcode = -1;
|
exitcode = -1;
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
app.reinit = false;
|
app.state = APP_STATE_RUNNING;
|
||||||
|
}
|
||||||
|
|
||||||
if (lgmpHostQueueNewSubs(app.pointerQueue) > 0)
|
if (lgmpHostQueueNewSubs(app.pointerQueue) > 0)
|
||||||
{
|
{
|
||||||
@@ -564,12 +601,7 @@ int app_main(int argc, char * argv[])
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
case CAPTURE_RESULT_REINIT:
|
case CAPTURE_RESULT_REINIT:
|
||||||
if (!captureRestart())
|
app.state = APP_STATE_RESTART;
|
||||||
{
|
|
||||||
exitcode = -1;
|
|
||||||
goto exit;
|
|
||||||
}
|
|
||||||
app.reinit = false;
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
case CAPTURE_RESULT_ERROR:
|
case CAPTURE_RESULT_ERROR:
|
||||||
@@ -579,6 +611,11 @@ int app_main(int argc, char * argv[])
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (app.state != APP_STATE_SHUTDOWN)
|
||||||
|
DEBUG_INFO("No subscribers, going to sleep...");
|
||||||
|
captureStop();
|
||||||
|
}
|
||||||
|
|
||||||
finish:
|
finish:
|
||||||
stopThreads();
|
stopThreads();
|
||||||
|
|
||||||
@@ -603,5 +640,5 @@ fail:
|
|||||||
|
|
||||||
void app_quit()
|
void app_quit()
|
||||||
{
|
{
|
||||||
app.running = false;
|
app.state = APP_STATE_SHUTDOWN;
|
||||||
}
|
}
|
||||||
|
|||||||
Submodule repos/LGMP updated: 6a41b4c237...2a1477550c
Reference in New Issue
Block a user