mirror of
https://github.com/gnif/LookingGlass.git
synced 2024-11-10 08:38:20 +00:00
bca54ab1f6
This changes the method of the memory copy from the host application to the guest. Instead of performing a full copy from the capture device into shared memory, and then flagging the new frame, we instead set a write pointer, flag the client that there is a new frame and then copy in chunks of 1024 bytes until the entire frame is copied. The client upon seeing the new frame flag begins to poll at high frequency the write pointer and upon each update copies as much as it can into the texture. This should improve latency but also slightly increase CPU usage on the client due to the high frequency polling.
1400 lines
35 KiB
C
1400 lines
35 KiB
C
/*
|
|
Looking Glass - KVM FrameRelay (KVMFR) Client
|
|
Copyright (C) 2017-2019 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 "main.h"
|
|
#include "config.h"
|
|
|
|
#include <getopt.h>
|
|
#include <signal.h>
|
|
#include <SDL2/SDL_syswm.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <assert.h>
|
|
|
|
#include "common/debug.h"
|
|
#include "common/crash.h"
|
|
#include "common/KVMFR.h"
|
|
#include "common/stringutils.h"
|
|
#include "utils.h"
|
|
#include "kb.h"
|
|
#include "ll.h"
|
|
|
|
// forwards
|
|
static int cursorThread(void * unused);
|
|
static int renderThread(void * unused);
|
|
static int frameThread (void * unused);
|
|
|
|
struct AppState state;
|
|
|
|
// this structure is initialized in config.c
|
|
struct AppParams params = { 0 };
|
|
|
|
static void updatePositionInfo()
|
|
{
|
|
if (state.haveSrcSize)
|
|
{
|
|
if (params.keepAspect)
|
|
{
|
|
const float srcAspect = (float)state.srcSize.y / (float)state.srcSize.x;
|
|
const float wndAspect = (float)state.windowH / (float)state.windowW;
|
|
if (wndAspect < srcAspect)
|
|
{
|
|
state.dstRect.w = (float)state.windowH / srcAspect;
|
|
state.dstRect.h = state.windowH;
|
|
state.dstRect.x = (state.windowW >> 1) - (state.dstRect.w >> 1);
|
|
state.dstRect.y = 0;
|
|
}
|
|
else
|
|
{
|
|
state.dstRect.w = state.windowW;
|
|
state.dstRect.h = (float)state.windowW * srcAspect;
|
|
state.dstRect.x = 0;
|
|
state.dstRect.y = (state.windowH >> 1) - (state.dstRect.h >> 1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
state.dstRect.x = 0;
|
|
state.dstRect.y = 0;
|
|
state.dstRect.w = state.windowW;
|
|
state.dstRect.h = state.windowH;
|
|
}
|
|
state.dstRect.valid = true;
|
|
|
|
state.scaleX = (float)state.srcSize.y / (float)state.dstRect.h;
|
|
state.scaleY = (float)state.srcSize.x / (float)state.dstRect.w;
|
|
}
|
|
|
|
state.lgrResize = true;
|
|
}
|
|
|
|
static int renderThread(void * unused)
|
|
{
|
|
if (!state.lgr->render_startup(state.lgrData, state.window))
|
|
{
|
|
state.running = false;
|
|
return 1;
|
|
}
|
|
|
|
// start the cursor thread after render startup to prevent a race condition
|
|
SDL_Thread *t_cursor = NULL;
|
|
if (!(t_cursor = SDL_CreateThread(cursorThread, "cursorThread", NULL)))
|
|
{
|
|
DEBUG_ERROR("cursor create thread failed");
|
|
return 1;
|
|
}
|
|
|
|
struct timespec time;
|
|
clock_gettime(CLOCK_MONOTONIC, &time);
|
|
|
|
while(state.running)
|
|
{
|
|
if (state.lgrResize)
|
|
{
|
|
if (state.lgr)
|
|
state.lgr->on_resize(state.lgrData, state.windowW, state.windowH, state.dstRect);
|
|
state.lgrResize = false;
|
|
}
|
|
|
|
if (!state.lgr->render(state.lgrData, state.window))
|
|
break;
|
|
|
|
if (params.showFPS)
|
|
{
|
|
const uint64_t t = nanotime();
|
|
state.renderTime += t - state.lastFrameTime;
|
|
state.lastFrameTime = t;
|
|
++state.renderCount;
|
|
|
|
if (state.renderTime > 1e9)
|
|
{
|
|
const float avgUPS = 1000.0f / (((float)state.renderTime / state.frameCount ) / 1e6f);
|
|
const float avgFPS = 1000.0f / (((float)state.renderTime / state.renderCount) / 1e6f);
|
|
state.lgr->update_fps(state.lgrData, avgUPS, avgFPS);
|
|
|
|
state.renderTime = 0;
|
|
state.frameCount = 0;
|
|
state.renderCount = 0;
|
|
}
|
|
}
|
|
|
|
uint64_t nsec = time.tv_nsec + state.frameTime;
|
|
if (nsec > 1e9)
|
|
{
|
|
time.tv_nsec = nsec - 1e9;
|
|
++time.tv_sec;
|
|
}
|
|
else
|
|
time.tv_nsec = nsec;
|
|
|
|
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &time, NULL);
|
|
}
|
|
|
|
state.running = false;
|
|
|
|
if (t_cursor)
|
|
SDL_WaitThread(t_cursor, NULL);
|
|
|
|
if (state.t_frame)
|
|
SDL_WaitThread(state.t_frame, NULL);
|
|
|
|
state.lgr->deinitialize(state.lgrData);
|
|
state.lgr = NULL;
|
|
return 0;
|
|
}
|
|
|
|
static int cursorThread(void * unused)
|
|
{
|
|
KVMFRCursor header;
|
|
LG_RendererCursor cursorType = LG_CURSOR_COLOR;
|
|
uint32_t version = 0;
|
|
|
|
memset(&header, 0, sizeof(KVMFRCursor));
|
|
|
|
while(state.running)
|
|
{
|
|
// poll until we have cursor data
|
|
if(!(state.shm->cursor.flags & KVMFR_CURSOR_FLAG_UPDATE) &&
|
|
!(state.shm->cursor.flags & KVMFR_CURSOR_FLAG_POS))
|
|
{
|
|
if (!state.running)
|
|
return 0;
|
|
usleep(params.cursorPollInterval);
|
|
continue;
|
|
}
|
|
|
|
// if the cursor was moved
|
|
bool moved = false;
|
|
if (state.shm->cursor.flags & KVMFR_CURSOR_FLAG_POS)
|
|
{
|
|
state.cursor.x = state.shm->cursor.x;
|
|
state.cursor.y = state.shm->cursor.y;
|
|
state.haveCursorPos = true;
|
|
moved = true;
|
|
}
|
|
|
|
// if this was only a move event
|
|
if (!(state.shm->cursor.flags & KVMFR_CURSOR_FLAG_UPDATE))
|
|
{
|
|
// turn off the pos flag, trigger the event and continue
|
|
__sync_and_and_fetch(&state.shm->cursor.flags, ~KVMFR_CURSOR_FLAG_POS);
|
|
|
|
state.lgr->on_mouse_event
|
|
(
|
|
state.lgrData,
|
|
state.cursorVisible,
|
|
state.cursor.x,
|
|
state.cursor.y
|
|
);
|
|
continue;
|
|
}
|
|
|
|
// we must take a copy of the header to prevent the contained arguments
|
|
// from being abused to overflow buffers.
|
|
memcpy(&header, &state.shm->cursor, sizeof(struct KVMFRCursor));
|
|
|
|
if (header.flags & KVMFR_CURSOR_FLAG_SHAPE &&
|
|
header.version != version)
|
|
{
|
|
version = header.version;
|
|
|
|
bool bad = false;
|
|
switch(header.type)
|
|
{
|
|
case CURSOR_TYPE_COLOR : cursorType = LG_CURSOR_COLOR ; break;
|
|
case CURSOR_TYPE_MONOCHROME : cursorType = LG_CURSOR_MONOCHROME ; break;
|
|
case CURSOR_TYPE_MASKED_COLOR: cursorType = LG_CURSOR_MASKED_COLOR; break;
|
|
default:
|
|
DEBUG_ERROR("Invalid cursor type");
|
|
bad = true;
|
|
break;
|
|
}
|
|
|
|
if (bad)
|
|
break;
|
|
|
|
// check the data position is sane
|
|
const uint64_t dataSize = header.height * header.pitch;
|
|
if (header.dataPos + dataSize > state.shmSize)
|
|
{
|
|
DEBUG_ERROR("The guest sent an invalid mouse dataPos");
|
|
break;
|
|
}
|
|
|
|
const uint8_t * data = (const uint8_t *)state.shm + header.dataPos;
|
|
if (!state.lgr->on_mouse_shape(
|
|
state.lgrData,
|
|
cursorType,
|
|
header.width,
|
|
header.height,
|
|
header.pitch,
|
|
data)
|
|
)
|
|
{
|
|
DEBUG_ERROR("Failed to update mouse shape");
|
|
break;
|
|
}
|
|
}
|
|
|
|
// now we have taken the mouse data, we can flag to the host we are ready
|
|
state.shm->cursor.flags = 0;
|
|
|
|
bool showCursor = header.flags & KVMFR_CURSOR_FLAG_VISIBLE;
|
|
if (showCursor != state.cursorVisible || moved)
|
|
{
|
|
state.cursorVisible = showCursor;
|
|
state.lgr->on_mouse_event
|
|
(
|
|
state.lgrData,
|
|
state.cursorVisible,
|
|
state.cursor.x,
|
|
state.cursor.y
|
|
);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int frameThread(void * unused)
|
|
{
|
|
bool error = false;
|
|
KVMFRFrame header;
|
|
|
|
memset(&header, 0, sizeof(struct KVMFRFrame));
|
|
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
|
|
|
|
while(state.running)
|
|
{
|
|
// poll until we have a new frame
|
|
while(!(state.shm->frame.flags & KVMFR_FRAME_FLAG_UPDATE))
|
|
{
|
|
if (!state.running)
|
|
break;
|
|
|
|
usleep(params.framePollInterval);
|
|
continue;
|
|
}
|
|
|
|
// we must take a copy of the header to prevent the contained
|
|
// arguments from being abused to overflow buffers.
|
|
memcpy(&header, &state.shm->frame, sizeof(struct KVMFRFrame));
|
|
|
|
// tell the host to continue as the host buffers up to one frame
|
|
// we can be sure the data for this frame wont be touched
|
|
__sync_and_and_fetch(&state.shm->frame.flags, ~KVMFR_FRAME_FLAG_UPDATE);
|
|
|
|
// sainty check of the frame format
|
|
if (
|
|
header.type >= FRAME_TYPE_MAX ||
|
|
header.width == 0 ||
|
|
header.height == 0 ||
|
|
header.pitch == 0 ||
|
|
header.dataPos == 0 ||
|
|
header.dataPos > state.shmSize ||
|
|
header.pitch < header.width
|
|
){
|
|
DEBUG_WARN("Bad header");
|
|
DEBUG_WARN(" width : %u" , header.width );
|
|
DEBUG_WARN(" height : %u" , header.height );
|
|
DEBUG_WARN(" pitch : %u" , header.pitch );
|
|
DEBUG_WARN(" dataPos: 0x%08lx", header.dataPos);
|
|
usleep(1000);
|
|
continue;
|
|
}
|
|
|
|
// setup the renderer format with the frame format details
|
|
LG_RendererFormat lgrFormat;
|
|
lgrFormat.type = header.type;
|
|
lgrFormat.width = header.width;
|
|
lgrFormat.height = header.height;
|
|
lgrFormat.stride = header.stride;
|
|
lgrFormat.pitch = header.pitch;
|
|
|
|
size_t dataSize;
|
|
switch(header.type)
|
|
{
|
|
case FRAME_TYPE_RGBA:
|
|
case FRAME_TYPE_BGRA:
|
|
case FRAME_TYPE_RGBA10:
|
|
dataSize = lgrFormat.height * lgrFormat.pitch;
|
|
lgrFormat.bpp = 32;
|
|
break;
|
|
|
|
case FRAME_TYPE_YUV420:
|
|
dataSize = lgrFormat.height * lgrFormat.width;
|
|
dataSize += (dataSize / 4) * 2;
|
|
lgrFormat.bpp = 12;
|
|
break;
|
|
|
|
default:
|
|
DEBUG_ERROR("Unsupported frameType");
|
|
error = true;
|
|
break;
|
|
}
|
|
|
|
if (error)
|
|
break;
|
|
|
|
// check the header's dataPos is sane
|
|
if (header.dataPos + dataSize > state.shmSize)
|
|
{
|
|
DEBUG_ERROR("The guest sent an invalid dataPos");
|
|
break;
|
|
}
|
|
|
|
if (header.width != state.srcSize.x || header.height != state.srcSize.y)
|
|
{
|
|
state.srcSize.x = header.width;
|
|
state.srcSize.y = header.height;
|
|
state.haveSrcSize = true;
|
|
if (params.autoResize)
|
|
SDL_SetWindowSize(state.window, header.width, header.height);
|
|
updatePositionInfo();
|
|
}
|
|
|
|
FrameBuffer frame = (FrameBuffer)((uint8_t *)state.shm + header.dataPos);
|
|
if (!state.lgr->on_frame_event(state.lgrData, lgrFormat, frame))
|
|
{
|
|
DEBUG_ERROR("renderer on frame event returned failure");
|
|
break;
|
|
}
|
|
|
|
++state.frameCount;
|
|
}
|
|
|
|
state.running = false;
|
|
return 0;
|
|
}
|
|
|
|
int spiceThread(void * arg)
|
|
{
|
|
while(state.running)
|
|
if (!spice_process())
|
|
{
|
|
if (state.running)
|
|
{
|
|
state.running = false;
|
|
DEBUG_ERROR("failed to process spice messages");
|
|
}
|
|
break;
|
|
}
|
|
|
|
state.running = false;
|
|
return 0;
|
|
}
|
|
|
|
static inline const uint32_t mapScancode(SDL_Scancode scancode)
|
|
{
|
|
uint32_t ps2;
|
|
if (scancode > (sizeof(usb_to_ps2) / sizeof(uint32_t)) || (ps2 = usb_to_ps2[scancode]) == 0)
|
|
{
|
|
DEBUG_WARN("Unable to map USB scan code: %x\n", scancode);
|
|
return 0;
|
|
}
|
|
return ps2;
|
|
}
|
|
|
|
static LG_ClipboardData spice_type_to_clipboard_type(const SpiceDataType type)
|
|
{
|
|
switch(type)
|
|
{
|
|
case SPICE_DATA_TEXT: return LG_CLIPBOARD_DATA_TEXT; break;
|
|
case SPICE_DATA_PNG : return LG_CLIPBOARD_DATA_PNG ; break;
|
|
case SPICE_DATA_BMP : return LG_CLIPBOARD_DATA_BMP ; break;
|
|
case SPICE_DATA_TIFF: return LG_CLIPBOARD_DATA_TIFF; break;
|
|
case SPICE_DATA_JPEG: return LG_CLIPBOARD_DATA_JPEG; break;
|
|
default:
|
|
DEBUG_ERROR("invalid spice data type");
|
|
return LG_CLIPBOARD_DATA_NONE;
|
|
}
|
|
}
|
|
|
|
static SpiceDataType clipboard_type_to_spice_type(const LG_ClipboardData type)
|
|
{
|
|
switch(type)
|
|
{
|
|
case LG_CLIPBOARD_DATA_TEXT: return SPICE_DATA_TEXT; break;
|
|
case LG_CLIPBOARD_DATA_PNG : return SPICE_DATA_PNG ; break;
|
|
case LG_CLIPBOARD_DATA_BMP : return SPICE_DATA_BMP ; break;
|
|
case LG_CLIPBOARD_DATA_TIFF: return SPICE_DATA_TIFF; break;
|
|
case LG_CLIPBOARD_DATA_JPEG: return SPICE_DATA_JPEG; break;
|
|
default:
|
|
DEBUG_ERROR("invalid clipboard data type");
|
|
return SPICE_DATA_NONE;
|
|
}
|
|
}
|
|
|
|
void clipboardRelease()
|
|
{
|
|
if (!params.clipboardToVM)
|
|
return;
|
|
|
|
spice_clipboard_release();
|
|
}
|
|
|
|
void clipboardNotify(const LG_ClipboardData type)
|
|
{
|
|
if (!params.clipboardToVM)
|
|
return;
|
|
|
|
if (type == LG_CLIPBOARD_DATA_NONE)
|
|
{
|
|
spice_clipboard_release();
|
|
return;
|
|
}
|
|
|
|
spice_clipboard_grab(clipboard_type_to_spice_type(type));
|
|
}
|
|
|
|
void clipboardData(const LG_ClipboardData type, uint8_t * data, size_t size)
|
|
{
|
|
if (!params.clipboardToVM)
|
|
return;
|
|
|
|
uint8_t * buffer = data;
|
|
|
|
// unix2dos
|
|
if (type == LG_CLIPBOARD_DATA_TEXT)
|
|
{
|
|
// TODO: make this more memory efficent
|
|
size_t newSize = 0;
|
|
buffer = malloc(size * 2);
|
|
uint8_t * p = buffer;
|
|
for(uint32_t i = 0; i < size; ++i)
|
|
{
|
|
uint8_t c = data[i];
|
|
if (c == '\n')
|
|
{
|
|
*p++ = '\r';
|
|
++newSize;
|
|
}
|
|
*p++ = c;
|
|
++newSize;
|
|
}
|
|
size = newSize;
|
|
}
|
|
|
|
spice_clipboard_data(clipboard_type_to_spice_type(type), buffer, (uint32_t)size);
|
|
if (buffer != data)
|
|
free(buffer);
|
|
}
|
|
|
|
void clipboardRequest(const LG_ClipboardReplyFn replyFn, void * opaque)
|
|
{
|
|
if (!params.clipboardToLocal)
|
|
return;
|
|
|
|
struct CBRequest * cbr = (struct CBRequest *)malloc(sizeof(struct CBRequest()));
|
|
|
|
cbr->type = state.cbType;
|
|
cbr->replyFn = replyFn;
|
|
cbr->opaque = opaque;
|
|
ll_push(state.cbRequestList, cbr);
|
|
|
|
spice_clipboard_request(state.cbType);
|
|
}
|
|
|
|
void spiceClipboardNotice(const SpiceDataType type)
|
|
{
|
|
if (!params.clipboardToLocal)
|
|
return;
|
|
|
|
if (!state.lgc || !state.lgc->notice)
|
|
return;
|
|
|
|
state.cbType = type;
|
|
state.lgc->notice(clipboardRequest, spice_type_to_clipboard_type(type));
|
|
}
|
|
|
|
void spiceClipboardData(const SpiceDataType type, uint8_t * buffer, uint32_t size)
|
|
{
|
|
if (!params.clipboardToLocal)
|
|
return;
|
|
|
|
if (type == SPICE_DATA_TEXT)
|
|
{
|
|
// dos2unix
|
|
uint8_t * p = buffer;
|
|
uint32_t newSize = size;
|
|
for(uint32_t i = 0; i < size; ++i)
|
|
{
|
|
uint8_t c = buffer[i];
|
|
if (c == '\r')
|
|
{
|
|
--newSize;
|
|
continue;
|
|
}
|
|
*p++ = c;
|
|
}
|
|
size = newSize;
|
|
}
|
|
|
|
struct CBRequest * cbr;
|
|
if (ll_shift(state.cbRequestList, (void **)&cbr))
|
|
{
|
|
cbr->replyFn(cbr->opaque, type, buffer, size);
|
|
free(cbr);
|
|
}
|
|
}
|
|
|
|
void spiceClipboardRelease()
|
|
{
|
|
if (!params.clipboardToLocal)
|
|
return;
|
|
|
|
if (state.lgc && state.lgc->release)
|
|
state.lgc->release();
|
|
}
|
|
|
|
void spiceClipboardRequest(const SpiceDataType type)
|
|
{
|
|
if (!params.clipboardToVM)
|
|
return;
|
|
|
|
if (state.lgc && state.lgc->request)
|
|
state.lgc->request(spice_type_to_clipboard_type(type));
|
|
}
|
|
|
|
int eventFilter(void * userdata, SDL_Event * event)
|
|
{
|
|
static bool serverMode = false;
|
|
static bool realignGuest = true;
|
|
|
|
switch(event->type)
|
|
{
|
|
case SDL_QUIT:
|
|
{
|
|
if (!params.ignoreQuit)
|
|
state.running = false;
|
|
return 0;
|
|
}
|
|
|
|
case SDL_WINDOWEVENT:
|
|
{
|
|
switch(event->window.event)
|
|
{
|
|
case SDL_WINDOWEVENT_ENTER:
|
|
realignGuest = true;
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
|
SDL_GetWindowSize(state.window, &state.windowW, &state.windowH);
|
|
updatePositionInfo();
|
|
realignGuest = true;
|
|
break;
|
|
|
|
// allow a window close event to close the application even if ignoreQuit is set
|
|
case SDL_WINDOWEVENT_CLOSE:
|
|
state.running = false;
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
case SDL_SYSWMEVENT:
|
|
{
|
|
if (params.useSpiceClipboard && state.lgc && state.lgc->wmevent)
|
|
state.lgc->wmevent(event->syswm.msg);
|
|
return 0;
|
|
}
|
|
|
|
case SDL_MOUSEMOTION:
|
|
{
|
|
if (state.ignoreInput || !params.useSpiceInput)
|
|
break;
|
|
|
|
if (
|
|
!serverMode && (
|
|
event->motion.x < state.dstRect.x ||
|
|
event->motion.x > state.dstRect.x + state.dstRect.w ||
|
|
event->motion.y < state.dstRect.y ||
|
|
event->motion.y > state.dstRect.y + state.dstRect.h
|
|
)
|
|
)
|
|
{
|
|
realignGuest = true;
|
|
break;
|
|
}
|
|
|
|
int x = 0;
|
|
int y = 0;
|
|
if (realignGuest && state.haveCursorPos)
|
|
{
|
|
x = event->motion.x - state.dstRect.x;
|
|
y = event->motion.y - state.dstRect.y;
|
|
if (params.scaleMouseInput && !serverMode)
|
|
{
|
|
x = (float)x * state.scaleX;
|
|
y = (float)y * state.scaleY;
|
|
}
|
|
x -= state.cursor.x;
|
|
y -= state.cursor.y;
|
|
realignGuest = false;
|
|
state.accX = 0;
|
|
state.accY = 0;
|
|
state.sensX = 0;
|
|
state.sensY = 0;
|
|
|
|
if (!spice_mouse_motion(x, y))
|
|
DEBUG_ERROR("SDL_MOUSEMOTION: failed to send message");
|
|
break;
|
|
}
|
|
|
|
x = event->motion.xrel;
|
|
y = event->motion.yrel;
|
|
if (x != 0 || y != 0)
|
|
{
|
|
if (params.scaleMouseInput && !serverMode)
|
|
{
|
|
state.accX += (float)x * state.scaleX;
|
|
state.accY += (float)y * state.scaleY;
|
|
x = floor(state.accX);
|
|
y = floor(state.accY);
|
|
state.accX -= x;
|
|
state.accY -= y;
|
|
}
|
|
|
|
if (serverMode && state.mouseSens != 0)
|
|
{
|
|
state.sensX += ((float)x / 10.0f) * (state.mouseSens + 10);
|
|
state.sensY += ((float)y / 10.0f) * (state.mouseSens + 10);
|
|
x = floor(state.sensX);
|
|
y = floor(state.sensY);
|
|
state.sensX -= x;
|
|
state.sensY -= y;
|
|
}
|
|
|
|
if (!spice_mouse_motion(x, y))
|
|
{
|
|
DEBUG_ERROR("SDL_MOUSEMOTION: failed to send message");
|
|
break;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SDL_KEYDOWN:
|
|
{
|
|
SDL_Scancode sc = event->key.keysym.scancode;
|
|
if (sc == params.escapeKey)
|
|
{
|
|
state.escapeActive = true;
|
|
state.escapeAction = -1;
|
|
break;
|
|
}
|
|
|
|
if (state.escapeActive)
|
|
{
|
|
state.escapeAction = sc;
|
|
break;
|
|
}
|
|
|
|
if (state.ignoreInput || !params.useSpiceInput)
|
|
break;
|
|
|
|
uint32_t scancode = mapScancode(sc);
|
|
if (scancode == 0)
|
|
break;
|
|
|
|
if (!state.keyDown[sc])
|
|
{
|
|
if (spice_key_down(scancode))
|
|
state.keyDown[sc] = true;
|
|
else
|
|
{
|
|
DEBUG_ERROR("SDL_KEYDOWN: failed to send message");
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SDL_KEYUP:
|
|
{
|
|
SDL_Scancode sc = event->key.keysym.scancode;
|
|
if (state.escapeActive)
|
|
{
|
|
if (state.escapeAction == -1)
|
|
{
|
|
if (params.useSpiceInput)
|
|
{
|
|
serverMode = !serverMode;
|
|
spice_mouse_mode(serverMode);
|
|
SDL_SetRelativeMouseMode(serverMode);
|
|
SDL_SetWindowGrab(state.window, serverMode);
|
|
DEBUG_INFO("Server Mode: %s", serverMode ? "on" : "off");
|
|
|
|
app_alert(
|
|
serverMode ? LG_ALERT_SUCCESS : LG_ALERT_WARNING,
|
|
serverMode ? "Capture Enabled" : "Capture Disabled"
|
|
);
|
|
|
|
if (!serverMode)
|
|
realignGuest = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
KeybindHandle handle = state.bindings[sc];
|
|
if (handle)
|
|
handle->callback(sc, handle->opaque);
|
|
}
|
|
|
|
if (sc == params.escapeKey)
|
|
state.escapeActive = false;
|
|
}
|
|
|
|
if (state.ignoreInput || !params.useSpiceInput)
|
|
break;
|
|
|
|
// avoid sending key up events when we didn't send a down
|
|
if (!state.keyDown[sc])
|
|
break;
|
|
|
|
uint32_t scancode = mapScancode(sc);
|
|
if (scancode == 0)
|
|
break;
|
|
|
|
if (spice_key_up(scancode))
|
|
state.keyDown[sc] = false;
|
|
else
|
|
{
|
|
DEBUG_ERROR("SDL_KEYUP: failed to send message");
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SDL_MOUSEWHEEL:
|
|
if (state.ignoreInput || !params.useSpiceInput)
|
|
break;
|
|
|
|
if (
|
|
!spice_mouse_press (event->wheel.y == 1 ? 4 : 5) ||
|
|
!spice_mouse_release(event->wheel.y == 1 ? 4 : 5)
|
|
)
|
|
{
|
|
DEBUG_ERROR("SDL_MOUSEWHEEL: failed to send messages");
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case SDL_MOUSEBUTTONDOWN:
|
|
if (state.ignoreInput || !params.useSpiceInput)
|
|
break;
|
|
|
|
// The SPICE protocol doesn't support more than a standard PS/2 3 button mouse
|
|
if (event->button.button > 3)
|
|
break;
|
|
if (
|
|
!spice_mouse_position(event->button.x, event->button.y) ||
|
|
!spice_mouse_press(event->button.button)
|
|
)
|
|
{
|
|
DEBUG_ERROR("SDL_MOUSEBUTTONDOWN: failed to send message");
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case SDL_MOUSEBUTTONUP:
|
|
if (state.ignoreInput || !params.useSpiceInput)
|
|
break;
|
|
|
|
// The SPICE protocol doesn't support more than a standard PS/2 3 button mouse
|
|
if (event->button.button > 3)
|
|
break;
|
|
if (
|
|
!spice_mouse_position(event->button.x, event->button.y) ||
|
|
!spice_mouse_release(event->button.button)
|
|
)
|
|
{
|
|
DEBUG_ERROR("SDL_MOUSEBUTTONUP: failed to send message");
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// consume all events
|
|
return 0;
|
|
}
|
|
|
|
void int_handler(int signal)
|
|
{
|
|
switch(signal)
|
|
{
|
|
case SIGINT:
|
|
case SIGTERM:
|
|
DEBUG_INFO("Caught signal, shutting down...");
|
|
state.running = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void * map_memory()
|
|
{
|
|
struct stat st;
|
|
if (stat(params.shmFile, &st) < 0)
|
|
{
|
|
DEBUG_ERROR("Failed to stat the shared memory file: %s", params.shmFile);
|
|
return NULL;
|
|
}
|
|
|
|
state.shmSize = params.shmSize ? params.shmSize : st.st_size;
|
|
state.shmFD = open(params.shmFile, O_RDWR, (mode_t)0600);
|
|
if (state.shmFD < 0)
|
|
{
|
|
DEBUG_ERROR("Failed to open the shared memory file: %s", params.shmFile);
|
|
return NULL;
|
|
}
|
|
|
|
void * map = mmap(0, state.shmSize, PROT_READ | PROT_WRITE, MAP_SHARED, state.shmFD, 0);
|
|
if (map == MAP_FAILED)
|
|
{
|
|
DEBUG_ERROR("Failed to map the shared memory file: %s", params.shmFile);
|
|
close(state.shmFD);
|
|
state.shmFD = 0;
|
|
return NULL;
|
|
}
|
|
|
|
return map;
|
|
}
|
|
|
|
static bool try_renderer(const int index, const LG_RendererParams lgrParams, Uint32 * sdlFlags)
|
|
{
|
|
const LG_Renderer *r = LG_Renderers[index];
|
|
|
|
if (!IS_LG_RENDERER_VALID(r))
|
|
{
|
|
DEBUG_ERROR("FIXME: Renderer %d is invalid, skipping", index);
|
|
return false;
|
|
}
|
|
|
|
// create the renderer
|
|
state.lgrData = NULL;
|
|
if (!r->create(&state.lgrData, lgrParams))
|
|
return false;
|
|
|
|
// initialize the renderer
|
|
if (!r->initialize(state.lgrData, sdlFlags))
|
|
{
|
|
r->deinitialize(state.lgrData);
|
|
return false;
|
|
}
|
|
|
|
DEBUG_INFO("Using Renderer: %s", r->get_name());
|
|
return true;
|
|
}
|
|
|
|
static void toggle_fullscreen(SDL_Scancode key, void * opaque)
|
|
{
|
|
SDL_SetWindowFullscreen(state.window, params.fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP);
|
|
params.fullscreen = !params.fullscreen;
|
|
}
|
|
|
|
static void toggle_input(SDL_Scancode key, void * opaque)
|
|
{
|
|
state.ignoreInput = !state.ignoreInput;
|
|
app_alert(
|
|
LG_ALERT_INFO,
|
|
state.ignoreInput ? "Input Disabled" : "Input Enabled"
|
|
);
|
|
}
|
|
|
|
static void mouse_sens_inc(SDL_Scancode key, void * opaque)
|
|
{
|
|
char * msg;
|
|
if (state.mouseSens < 9)
|
|
++state.mouseSens;
|
|
|
|
alloc_sprintf(&msg, "Sensitivity: %s%d", state.mouseSens > 0 ? "+" : "", state.mouseSens);
|
|
app_alert(
|
|
LG_ALERT_INFO,
|
|
msg
|
|
);
|
|
free(msg);
|
|
}
|
|
|
|
static void mouse_sens_dec(SDL_Scancode key, void * opaque)
|
|
{
|
|
char * msg;
|
|
|
|
if (state.mouseSens > -9)
|
|
--state.mouseSens;
|
|
|
|
alloc_sprintf(&msg, "Sensitivity: %s%d", state.mouseSens > 0 ? "+" : "", state.mouseSens);
|
|
app_alert(
|
|
LG_ALERT_INFO,
|
|
msg
|
|
);
|
|
free(msg);
|
|
}
|
|
|
|
static void ctrl_alt_fn(SDL_Scancode key, void * opaque)
|
|
{
|
|
const uint32_t ctrl = mapScancode(SDL_SCANCODE_LCTRL);
|
|
const uint32_t alt = mapScancode(SDL_SCANCODE_LALT );
|
|
const uint32_t fn = mapScancode(key);
|
|
|
|
spice_key_down(ctrl);
|
|
spice_key_down(alt );
|
|
spice_key_down(fn );
|
|
|
|
spice_key_up(ctrl);
|
|
spice_key_up(alt );
|
|
spice_key_up(fn );
|
|
}
|
|
|
|
static void register_key_binds()
|
|
{
|
|
state.kbFS = app_register_keybind(SDL_SCANCODE_F , toggle_fullscreen, NULL);
|
|
state.kbInput = app_register_keybind(SDL_SCANCODE_I , toggle_input , NULL);
|
|
state.kbMouseSensInc = app_register_keybind(SDL_SCANCODE_INSERT, mouse_sens_inc , NULL);
|
|
state.kbMouseSensDec = app_register_keybind(SDL_SCANCODE_DELETE, mouse_sens_dec , NULL);
|
|
|
|
state.kbCtrlAltFn[0 ] = app_register_keybind(SDL_SCANCODE_F1 , ctrl_alt_fn, NULL);
|
|
state.kbCtrlAltFn[1 ] = app_register_keybind(SDL_SCANCODE_F2 , ctrl_alt_fn, NULL);
|
|
state.kbCtrlAltFn[2 ] = app_register_keybind(SDL_SCANCODE_F3 , ctrl_alt_fn, NULL);
|
|
state.kbCtrlAltFn[3 ] = app_register_keybind(SDL_SCANCODE_F4 , ctrl_alt_fn, NULL);
|
|
state.kbCtrlAltFn[4 ] = app_register_keybind(SDL_SCANCODE_F5 , ctrl_alt_fn, NULL);
|
|
state.kbCtrlAltFn[5 ] = app_register_keybind(SDL_SCANCODE_F6 , ctrl_alt_fn, NULL);
|
|
state.kbCtrlAltFn[6 ] = app_register_keybind(SDL_SCANCODE_F7 , ctrl_alt_fn, NULL);
|
|
state.kbCtrlAltFn[7 ] = app_register_keybind(SDL_SCANCODE_F8 , ctrl_alt_fn, NULL);
|
|
state.kbCtrlAltFn[8 ] = app_register_keybind(SDL_SCANCODE_F9 , ctrl_alt_fn, NULL);
|
|
state.kbCtrlAltFn[9 ] = app_register_keybind(SDL_SCANCODE_F10, ctrl_alt_fn, NULL);
|
|
state.kbCtrlAltFn[10] = app_register_keybind(SDL_SCANCODE_F11, ctrl_alt_fn, NULL);
|
|
state.kbCtrlAltFn[11] = app_register_keybind(SDL_SCANCODE_F12, ctrl_alt_fn, NULL);
|
|
}
|
|
|
|
static void release_key_binds()
|
|
{
|
|
app_release_keybind(&state.kbFS);
|
|
app_release_keybind(&state.kbInput);
|
|
for(int i = 0; i < 12; ++i)
|
|
app_release_keybind(&state.kbCtrlAltFn[i]);
|
|
}
|
|
|
|
int run()
|
|
{
|
|
DEBUG_INFO("Looking Glass (" BUILD_VERSION ")");
|
|
DEBUG_INFO("Locking Method: " LG_LOCK_MODE);
|
|
|
|
memset(&state, 0, sizeof(state));
|
|
state.running = true;
|
|
state.scaleX = 1.0f;
|
|
state.scaleY = 1.0f;
|
|
|
|
state.mouseSens = params.mouseSens;
|
|
if (state.mouseSens < -9) state.mouseSens = -9;
|
|
else if (state.mouseSens > 9) state.mouseSens = 9;
|
|
|
|
char* XDG_SESSION_TYPE = getenv("XDG_SESSION_TYPE");
|
|
|
|
if (XDG_SESSION_TYPE == NULL)
|
|
XDG_SESSION_TYPE = "unspecified";
|
|
|
|
if (strcmp(XDG_SESSION_TYPE, "wayland") == 0)
|
|
{
|
|
DEBUG_INFO("Wayland detected");
|
|
if (getenv("SDL_VIDEODRIVER") == NULL)
|
|
{
|
|
int err = setenv("SDL_VIDEODRIVER", "wayland", 1);
|
|
if (err < 0)
|
|
{
|
|
DEBUG_ERROR("Unable to set the env variable SDL_VIDEODRIVER: %d", err);
|
|
return -1;
|
|
}
|
|
DEBUG_INFO("SDL_VIDEODRIVER has been set to wayland");
|
|
}
|
|
}
|
|
|
|
// warn about using FPS display until we can fix the font rendering to prevent lag spikes
|
|
if (params.showFPS)
|
|
{
|
|
DEBUG_WARN("================================================================================");
|
|
DEBUG_WARN("WARNING: The FPS display causes microstutters, this is a known issue" );
|
|
DEBUG_WARN("================================================================================");
|
|
}
|
|
|
|
if (SDL_Init(SDL_INIT_VIDEO) < 0)
|
|
{
|
|
DEBUG_ERROR("SDL_Init Failed");
|
|
return -1;
|
|
}
|
|
|
|
// override SDL's SIGINIT handler so that we can tell the difference between
|
|
// SIGINT and the user sending a close event, such as ALT+F4
|
|
signal(SIGINT , int_handler);
|
|
signal(SIGTERM, int_handler);
|
|
|
|
LG_RendererParams lgrParams;
|
|
lgrParams.showFPS = params.showFPS;
|
|
Uint32 sdlFlags;
|
|
|
|
if (params.forceRenderer)
|
|
{
|
|
DEBUG_INFO("Trying forced renderer");
|
|
sdlFlags = 0;
|
|
if (!try_renderer(params.forceRendererIndex, lgrParams, &sdlFlags))
|
|
{
|
|
DEBUG_ERROR("Forced renderer failed to iniailize");
|
|
return -1;
|
|
}
|
|
state.lgr = LG_Renderers[params.forceRendererIndex];
|
|
}
|
|
else
|
|
{
|
|
// probe for a a suitable renderer
|
|
for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i)
|
|
{
|
|
sdlFlags = 0;
|
|
if (try_renderer(i, lgrParams, &sdlFlags))
|
|
{
|
|
state.lgr = LG_Renderers[i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!state.lgr)
|
|
{
|
|
DEBUG_INFO("Unable to find a suitable renderer");
|
|
return -1;
|
|
}
|
|
|
|
state.window = SDL_CreateWindow(
|
|
params.windowTitle,
|
|
params.center ? SDL_WINDOWPOS_CENTERED : params.x,
|
|
params.center ? SDL_WINDOWPOS_CENTERED : params.y,
|
|
params.w,
|
|
params.h,
|
|
(
|
|
SDL_WINDOW_SHOWN |
|
|
(params.fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0) |
|
|
(params.allowResize ? SDL_WINDOW_RESIZABLE : 0) |
|
|
(params.borderless ? SDL_WINDOW_BORDERLESS : 0) |
|
|
(params.maximize ? SDL_WINDOW_MAXIMIZED : 0) |
|
|
sdlFlags
|
|
)
|
|
);
|
|
|
|
if (state.window == NULL)
|
|
{
|
|
DEBUG_ERROR("Could not create an SDL window: %s\n", SDL_GetError());
|
|
return 1;
|
|
}
|
|
|
|
if (params.fullscreen || !params.minimizeOnFocusLoss)
|
|
SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
|
|
|
|
if (!params.noScreensaver)
|
|
{
|
|
SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1");
|
|
SDL_EnableScreenSaver();
|
|
}
|
|
|
|
if (!params.center)
|
|
SDL_SetWindowPosition(state.window, params.x, params.y);
|
|
|
|
// ensure the initial window size is stored in the state
|
|
SDL_GetWindowSize(state.window, &state.windowW, &state.windowH);
|
|
|
|
// ensure renderer viewport is aware of the current window size
|
|
updatePositionInfo();
|
|
|
|
//Auto detect active monitor refresh rate for FPS Limit if no FPS Limit was passed.
|
|
if (params.fpsLimit == -1)
|
|
{
|
|
SDL_DisplayMode current;
|
|
if (SDL_GetCurrentDisplayMode(SDL_GetWindowDisplayIndex(state.window), ¤t) == 0)
|
|
{
|
|
state.frameTime = 1e9 / (current.refresh_rate * 2);
|
|
}
|
|
else
|
|
{
|
|
DEBUG_WARN("Unable to capture monitor refresh rate using the default FPS Limit: 200");
|
|
state.frameTime = 1e9 / 200;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DEBUG_INFO("Using the FPS Limit from args: %d", params.fpsLimit);
|
|
state.frameTime = 1e9 / params.fpsLimit;
|
|
}
|
|
|
|
register_key_binds();
|
|
|
|
// set the compositor hint to bypass for low latency
|
|
SDL_SysWMinfo wminfo;
|
|
SDL_VERSION(&wminfo.version);
|
|
if (SDL_GetWindowWMInfo(state.window, &wminfo))
|
|
{
|
|
if (wminfo.subsystem == SDL_SYSWM_X11)
|
|
{
|
|
Atom NETWM_BYPASS_COMPOSITOR = XInternAtom(
|
|
wminfo.info.x11.display,
|
|
"NETWM_BYPASS_COMPOSITOR",
|
|
False);
|
|
|
|
unsigned long value = 1;
|
|
XChangeProperty(
|
|
wminfo.info.x11.display,
|
|
wminfo.info.x11.window,
|
|
NETWM_BYPASS_COMPOSITOR,
|
|
XA_CARDINAL,
|
|
32,
|
|
PropModeReplace,
|
|
(unsigned char *)&value,
|
|
1
|
|
);
|
|
|
|
state.lgc = LG_Clipboards[0];
|
|
}
|
|
} else {
|
|
DEBUG_ERROR("Could not get SDL window information %s", SDL_GetError());
|
|
return -1;
|
|
}
|
|
|
|
if (!state.window)
|
|
{
|
|
DEBUG_ERROR("failed to create window");
|
|
return -1;
|
|
}
|
|
|
|
if (state.lgc)
|
|
{
|
|
DEBUG_INFO("Using Clipboard: %s", state.lgc->getName());
|
|
if (!state.lgc->init(&wminfo, clipboardRelease, clipboardNotify, clipboardData))
|
|
{
|
|
DEBUG_WARN("Failed to initialize the clipboard interface, continuing anyway");
|
|
state.lgc = NULL;
|
|
}
|
|
|
|
state.cbRequestList = ll_new();
|
|
}
|
|
|
|
SDL_Cursor *cursor = NULL;
|
|
if (params.hideMouse)
|
|
{
|
|
// work around SDL_ShowCursor being non functional
|
|
int32_t cursorData[2] = {0, 0};
|
|
cursor = SDL_CreateCursor((uint8_t*)cursorData, (uint8_t*)cursorData, 8, 8, 4, 4);
|
|
SDL_SetCursor(cursor);
|
|
SDL_ShowCursor(SDL_DISABLE);
|
|
}
|
|
|
|
SDL_Thread *t_spice = NULL;
|
|
SDL_Thread *t_render = NULL;
|
|
|
|
while(1)
|
|
{
|
|
state.shm = (struct KVMFRHeader *)map_memory();
|
|
if (!state.shm)
|
|
{
|
|
DEBUG_ERROR("Failed to map memory");
|
|
break;
|
|
}
|
|
|
|
// start the renderThread so we don't just display junk
|
|
if (!(t_render = SDL_CreateThread(renderThread, "renderThread", NULL)))
|
|
{
|
|
DEBUG_ERROR("render create thread failed");
|
|
break;
|
|
}
|
|
|
|
if (params.useSpiceInput || params.useSpiceClipboard)
|
|
{
|
|
spice_set_clipboard_cb(
|
|
spiceClipboardNotice,
|
|
spiceClipboardData,
|
|
spiceClipboardRelease,
|
|
spiceClipboardRequest);
|
|
|
|
if (!spice_connect(params.spiceHost, params.spicePort, ""))
|
|
{
|
|
DEBUG_ERROR("Failed to connect to spice server");
|
|
return 0;
|
|
}
|
|
|
|
while(state.running && !spice_ready())
|
|
if (!spice_process())
|
|
{
|
|
state.running = false;
|
|
DEBUG_ERROR("Failed to process spice messages");
|
|
break;
|
|
}
|
|
|
|
if (!(t_spice = SDL_CreateThread(spiceThread, "spiceThread", NULL)))
|
|
{
|
|
DEBUG_ERROR("spice create thread failed");
|
|
break;
|
|
}
|
|
}
|
|
|
|
// ensure mouse acceleration is identical in server mode
|
|
SDL_SetHintWithPriority(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, "1", SDL_HINT_OVERRIDE);
|
|
SDL_SetEventFilter(eventFilter, NULL);
|
|
|
|
// flag the host that we are starting up this is important so that
|
|
// the host wakes up if it is waiting on an interrupt, the host will
|
|
// also send us the current mouse shape since we won't know it yet
|
|
DEBUG_INFO("Waiting for host to signal it's ready...");
|
|
__sync_or_and_fetch(&state.shm->flags, KVMFR_HEADER_FLAG_RESTART);
|
|
|
|
while(state.running && (state.shm->flags & KVMFR_HEADER_FLAG_RESTART))
|
|
SDL_WaitEventTimeout(NULL, 1000);
|
|
|
|
if (!state.running)
|
|
break;
|
|
|
|
DEBUG_INFO("Host ready, starting session");
|
|
|
|
// check the header's magic and version are valid
|
|
if (memcmp(state.shm->magic, KVMFR_HEADER_MAGIC, sizeof(KVMFR_HEADER_MAGIC)) != 0)
|
|
{
|
|
DEBUG_ERROR("Invalid header magic, is the host running?");
|
|
break;
|
|
}
|
|
|
|
if (state.shm->version != KVMFR_HEADER_VERSION)
|
|
{
|
|
DEBUG_ERROR("KVMFR version missmatch, expected %u but got %u", KVMFR_HEADER_VERSION, state.shm->version);
|
|
DEBUG_ERROR("This is not a bug, ensure you have the right version of looking-glass-host.exe on the guest");
|
|
break;
|
|
}
|
|
|
|
if (!(state.t_frame = SDL_CreateThread(frameThread, "frameThread", NULL)))
|
|
{
|
|
DEBUG_ERROR("frame create thread failed");
|
|
break;
|
|
}
|
|
|
|
bool *closeAlert = NULL;
|
|
while(state.running)
|
|
{
|
|
SDL_WaitEventTimeout(NULL, 1000);
|
|
|
|
if (closeAlert == NULL)
|
|
{
|
|
if (state.shm->flags & KVMFR_HEADER_FLAG_PAUSED)
|
|
{
|
|
if (state.lgr && params.showAlerts)
|
|
state.lgr->on_alert(
|
|
state.lgrData,
|
|
LG_ALERT_WARNING,
|
|
"Stream Paused",
|
|
&closeAlert
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!(state.shm->flags & KVMFR_HEADER_FLAG_PAUSED))
|
|
{
|
|
*closeAlert = true;
|
|
closeAlert = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
state.running = false;
|
|
|
|
if (t_render)
|
|
SDL_WaitThread(t_render, NULL);
|
|
|
|
// if spice is still connected send key up events for any pressed keys
|
|
if (params.useSpiceInput && spice_ready())
|
|
{
|
|
for(int i = 0; i < SDL_NUM_SCANCODES; ++i)
|
|
if (state.keyDown[i])
|
|
{
|
|
uint32_t scancode = mapScancode(i);
|
|
if (scancode == 0)
|
|
continue;
|
|
state.keyDown[i] = false;
|
|
spice_key_up(scancode);
|
|
}
|
|
|
|
if (t_spice)
|
|
SDL_WaitThread(t_spice, NULL);
|
|
|
|
spice_disconnect();
|
|
}
|
|
|
|
if (state.lgc)
|
|
{
|
|
state.lgc->free();
|
|
|
|
struct CBRequest *cbr;
|
|
while(ll_shift(state.cbRequestList, (void **)&cbr))
|
|
free(cbr);
|
|
ll_free(state.cbRequestList);
|
|
}
|
|
|
|
if (state.window)
|
|
SDL_DestroyWindow(state.window);
|
|
|
|
if (cursor)
|
|
SDL_FreeCursor(cursor);
|
|
|
|
if (state.shm)
|
|
{
|
|
munmap(state.shm, state.shmSize);
|
|
close(state.shmFD);
|
|
}
|
|
|
|
SDL_Quit();
|
|
return 0;
|
|
}
|
|
|
|
int main(int argc, char * argv[])
|
|
{
|
|
if (!installCrashHandler("/proc/self/exe"))
|
|
DEBUG_WARN("Failed to install the crash handler");
|
|
|
|
config_init();
|
|
|
|
// early renderer setup for option registration
|
|
for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i)
|
|
LG_Renderers[i]->setup();
|
|
|
|
if (!config_load(argc, argv))
|
|
return -1;
|
|
|
|
if (params.grabKeyboard)
|
|
SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "1");
|
|
|
|
const int ret = run();
|
|
release_key_binds();
|
|
|
|
config_free();
|
|
return ret;
|
|
} |