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