mirror of
https://github.com/gnif/LookingGlass.git
synced 2024-11-13 01:38:20 +00:00
0690eacc9b
If the guest cursor state & position is unknown we can not rely on the information to detect edge crossings. As such only allow cursor input if LG is operating in capture mode.
2147 lines
52 KiB
C
2147 lines
52 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 <stdatomic.h>
|
|
|
|
#include "common/debug.h"
|
|
#include "common/crash.h"
|
|
#include "common/KVMFR.h"
|
|
#include "common/stringutils.h"
|
|
#include "common/thread.h"
|
|
#include "common/locking.h"
|
|
#include "common/event.h"
|
|
#include "common/ivshmem.h"
|
|
#include "common/time.h"
|
|
#include "common/version.h"
|
|
|
|
#include "utils.h"
|
|
#include "kb.h"
|
|
#include "ll.h"
|
|
|
|
#define RESIZE_TIMEOUT (10 * 1000) // 10ms
|
|
|
|
// forwards
|
|
static int cursorThread(void * unused);
|
|
static int renderThread(void * unused);
|
|
static int frameThread (void * unused);
|
|
|
|
static LGEvent *e_startup = NULL;
|
|
static LGEvent *e_frame = NULL;
|
|
static LGThread *t_spice = NULL;
|
|
static LGThread *t_render = NULL;
|
|
static LGThread *t_cursor = NULL;
|
|
static LGThread *t_frame = NULL;
|
|
static SDL_Cursor *cursor = NULL;
|
|
|
|
static Uint32 e_SDLEvent; // our SDL event
|
|
|
|
enum
|
|
{
|
|
LG_EVENT_ALIGN_TO_GUEST
|
|
};
|
|
|
|
struct AppState g_state;
|
|
struct CursorState g_cursor;
|
|
|
|
// this structure is initialized in config.c
|
|
struct AppParams params = { 0 };
|
|
|
|
static void setGrab(bool enable);
|
|
static void setGrabQuiet(bool enable);
|
|
static void setCursorInView(bool enable);
|
|
|
|
static void lgInit(void)
|
|
{
|
|
g_state.state = APP_STATE_RUNNING;
|
|
g_state.resizeDone = true;
|
|
|
|
g_cursor.useScale = false;
|
|
g_cursor.scale.x = 1.0;
|
|
g_cursor.scale.y = 1.0;
|
|
g_cursor.draw = true;
|
|
g_cursor.inView = false;
|
|
g_cursor.guest.valid = false;
|
|
}
|
|
|
|
bool app_getProp(LG_DSProperty prop, void * ret)
|
|
{
|
|
return g_state.ds->getProp(prop, ret);
|
|
}
|
|
|
|
SDL_Window * app_getWindow(void)
|
|
{
|
|
return g_state.window;
|
|
}
|
|
|
|
bool app_inputEnabled(void)
|
|
{
|
|
return params.useSpiceInput && !g_state.ignoreInput &&
|
|
((g_cursor.grab && params.captureInputOnly) || !params.captureInputOnly);
|
|
}
|
|
|
|
bool app_cursorInWindow(void)
|
|
{
|
|
return g_cursor.inWindow;
|
|
}
|
|
|
|
bool app_cursorIsGrabbed(void)
|
|
{
|
|
return g_cursor.grab;
|
|
}
|
|
|
|
bool app_cursorWantsRaw(void)
|
|
{
|
|
return params.rawMouse;
|
|
}
|
|
|
|
void app_updateCursorPos(double x, double y)
|
|
{
|
|
g_cursor.pos.x = x;
|
|
g_cursor.pos.y = y;
|
|
}
|
|
|
|
static void alignToGuest(void)
|
|
{
|
|
if (SDL_HasEvent(e_SDLEvent))
|
|
return;
|
|
|
|
SDL_Event event;
|
|
SDL_memset(&event, 0, sizeof(event));
|
|
event.type = e_SDLEvent;
|
|
event.user.code = LG_EVENT_ALIGN_TO_GUEST;
|
|
|
|
SDL_PushEvent(&event);
|
|
}
|
|
|
|
static void updatePositionInfo(void)
|
|
{
|
|
if (g_state.haveSrcSize)
|
|
{
|
|
if (params.keepAspect)
|
|
{
|
|
const float srcAspect = (float)g_state.srcSize.y / (float)g_state.srcSize.x;
|
|
const float wndAspect = (float)g_state.windowH / (float)g_state.windowW;
|
|
bool force = true;
|
|
|
|
if (params.dontUpscale &&
|
|
g_state.srcSize.x <= g_state.windowW &&
|
|
g_state.srcSize.y <= g_state.windowH)
|
|
{
|
|
force = false;
|
|
g_state.dstRect.w = g_state.srcSize.x;
|
|
g_state.dstRect.h = g_state.srcSize.y;
|
|
g_state.dstRect.x = g_state.windowCX - g_state.srcSize.x / 2;
|
|
g_state.dstRect.y = g_state.windowCY - g_state.srcSize.y / 2;
|
|
}
|
|
else
|
|
if ((int)(wndAspect * 1000) == (int)(srcAspect * 1000))
|
|
{
|
|
force = false;
|
|
g_state.dstRect.w = g_state.windowW;
|
|
g_state.dstRect.h = g_state.windowH;
|
|
g_state.dstRect.x = 0;
|
|
g_state.dstRect.y = 0;
|
|
}
|
|
else
|
|
if (wndAspect < srcAspect)
|
|
{
|
|
g_state.dstRect.w = (float)g_state.windowH / srcAspect;
|
|
g_state.dstRect.h = g_state.windowH;
|
|
g_state.dstRect.x = (g_state.windowW >> 1) - (g_state.dstRect.w >> 1);
|
|
g_state.dstRect.y = 0;
|
|
}
|
|
else
|
|
{
|
|
g_state.dstRect.w = g_state.windowW;
|
|
g_state.dstRect.h = (float)g_state.windowW * srcAspect;
|
|
g_state.dstRect.x = 0;
|
|
g_state.dstRect.y = (g_state.windowH >> 1) - (g_state.dstRect.h >> 1);
|
|
}
|
|
|
|
if (force && params.forceAspect)
|
|
{
|
|
g_state.resizeTimeout = microtime() + RESIZE_TIMEOUT;
|
|
g_state.resizeDone = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g_state.dstRect.x = 0;
|
|
g_state.dstRect.y = 0;
|
|
g_state.dstRect.w = g_state.windowW;
|
|
g_state.dstRect.h = g_state.windowH;
|
|
}
|
|
g_state.dstRect.valid = true;
|
|
|
|
g_cursor.useScale = (
|
|
g_state.srcSize.y != g_state.dstRect.h ||
|
|
g_state.srcSize.x != g_state.dstRect.w ||
|
|
g_cursor.guest.dpiScale != 100);
|
|
|
|
g_cursor.scale.x = (float)g_state.srcSize.y / (float)g_state.dstRect.h;
|
|
g_cursor.scale.y = (float)g_state.srcSize.x / (float)g_state.dstRect.w;
|
|
g_cursor.dpiScale = g_cursor.guest.dpiScale / 100.0f;
|
|
}
|
|
|
|
atomic_fetch_add(&g_state.lgrResize, 1);
|
|
}
|
|
|
|
static int renderThread(void * unused)
|
|
{
|
|
if (!g_state.lgr->render_startup(g_state.lgrData, g_state.window))
|
|
{
|
|
g_state.state = APP_STATE_SHUTDOWN;
|
|
|
|
/* unblock threads waiting on the condition */
|
|
lgSignalEvent(e_startup);
|
|
return 1;
|
|
}
|
|
|
|
/* signal to other threads that the renderer is ready */
|
|
lgSignalEvent(e_startup);
|
|
|
|
struct timespec time;
|
|
clock_gettime(CLOCK_MONOTONIC, &time);
|
|
|
|
while(g_state.state != APP_STATE_SHUTDOWN)
|
|
{
|
|
if (params.fpsMin != 0)
|
|
{
|
|
lgWaitEventAbs(e_frame, &time);
|
|
clock_gettime(CLOCK_MONOTONIC, &time);
|
|
tsAdd(&time, g_state.frameTime);
|
|
}
|
|
|
|
int resize = atomic_load(&g_state.lgrResize);
|
|
if (resize)
|
|
{
|
|
if (g_state.lgr)
|
|
g_state.lgr->on_resize(g_state.lgrData, g_state.windowW, g_state.windowH, g_state.dstRect);
|
|
atomic_compare_exchange_weak(&g_state.lgrResize, &resize, 0);
|
|
}
|
|
|
|
if (!g_state.lgr->render(g_state.lgrData, g_state.window))
|
|
break;
|
|
|
|
if (params.showFPS)
|
|
{
|
|
const uint64_t t = nanotime();
|
|
g_state.renderTime += t - g_state.lastFrameTime;
|
|
g_state.lastFrameTime = t;
|
|
++g_state.renderCount;
|
|
|
|
if (g_state.renderTime > 1e9)
|
|
{
|
|
const float avgUPS = 1000.0f / (((float)g_state.renderTime /
|
|
atomic_exchange_explicit(&g_state.frameCount, 0, memory_order_acquire)) /
|
|
1e6f);
|
|
|
|
const float avgFPS = 1000.0f / (((float)g_state.renderTime /
|
|
g_state.renderCount) /
|
|
1e6f);
|
|
|
|
g_state.lgr->update_fps(g_state.lgrData, avgUPS, avgFPS);
|
|
|
|
g_state.renderTime = 0;
|
|
g_state.renderCount = 0;
|
|
}
|
|
}
|
|
|
|
if (!g_state.resizeDone && g_state.resizeTimeout < microtime())
|
|
{
|
|
SDL_SetWindowSize(
|
|
g_state.window,
|
|
g_state.dstRect.w,
|
|
g_state.dstRect.h
|
|
);
|
|
g_state.resizeDone = true;
|
|
}
|
|
}
|
|
|
|
g_state.state = APP_STATE_SHUTDOWN;
|
|
|
|
if (t_cursor)
|
|
lgJoinThread(t_cursor, NULL);
|
|
|
|
if (t_frame)
|
|
lgJoinThread(t_frame, NULL);
|
|
|
|
g_state.lgr->deinitialize(g_state.lgrData);
|
|
g_state.lgr = NULL;
|
|
return 0;
|
|
}
|
|
|
|
static int cursorThread(void * unused)
|
|
{
|
|
LGMP_STATUS status;
|
|
PLGMPClientQueue queue;
|
|
LG_RendererCursor cursorType = LG_CURSOR_COLOR;
|
|
|
|
lgWaitEvent(e_startup, TIMEOUT_INFINITE);
|
|
|
|
// subscribe to the pointer queue
|
|
while(g_state.state == APP_STATE_RUNNING)
|
|
{
|
|
status = lgmpClientSubscribe(g_state.lgmp, LGMP_Q_POINTER, &queue);
|
|
if (status == LGMP_OK)
|
|
break;
|
|
|
|
if (status == LGMP_ERR_NO_SUCH_QUEUE)
|
|
{
|
|
usleep(1000);
|
|
continue;
|
|
}
|
|
|
|
DEBUG_ERROR("lgmpClientSubscribe Failed: %s", lgmpStatusString(status));
|
|
g_state.state = APP_STATE_SHUTDOWN;
|
|
break;
|
|
}
|
|
|
|
while(g_state.state == APP_STATE_RUNNING)
|
|
{
|
|
LGMPMessage msg;
|
|
if ((status = lgmpClientProcess(queue, &msg)) != LGMP_OK)
|
|
{
|
|
if (status == LGMP_ERR_QUEUE_EMPTY)
|
|
{
|
|
if (g_cursor.redraw && g_cursor.guest.valid)
|
|
{
|
|
g_cursor.redraw = false;
|
|
g_state.lgr->on_mouse_event
|
|
(
|
|
g_state.lgrData,
|
|
g_cursor.guest.visible && g_cursor.draw,
|
|
g_cursor.guest.x,
|
|
g_cursor.guest.y
|
|
);
|
|
|
|
lgSignalEvent(e_frame);
|
|
}
|
|
|
|
const struct timespec req =
|
|
{
|
|
.tv_sec = 0,
|
|
.tv_nsec = params.cursorPollInterval * 1000L
|
|
};
|
|
|
|
struct timespec rem;
|
|
while(nanosleep(&req, &rem) < 0)
|
|
if (errno != -EINTR)
|
|
{
|
|
DEBUG_ERROR("nanosleep failed");
|
|
break;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (status == LGMP_ERR_INVALID_SESSION)
|
|
g_state.state = APP_STATE_RESTART;
|
|
else
|
|
{
|
|
DEBUG_ERROR("lgmpClientProcess Failed: %s", lgmpStatusString(status));
|
|
g_state.state = APP_STATE_SHUTDOWN;
|
|
}
|
|
break;
|
|
}
|
|
|
|
KVMFRCursor * cursor = (KVMFRCursor *)msg.mem;
|
|
|
|
g_cursor.guest.visible =
|
|
msg.udata & CURSOR_FLAG_VISIBLE;
|
|
|
|
if (msg.udata & CURSOR_FLAG_SHAPE)
|
|
{
|
|
switch(cursor->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");
|
|
lgmpClientMessageDone(queue);
|
|
continue;
|
|
}
|
|
|
|
g_cursor.guest.hx = cursor->hx;
|
|
g_cursor.guest.hy = cursor->hy;
|
|
|
|
const uint8_t * data = (const uint8_t *)(cursor + 1);
|
|
if (!g_state.lgr->on_mouse_shape(
|
|
g_state.lgrData,
|
|
cursorType,
|
|
cursor->width,
|
|
cursor->height,
|
|
params.winRotate,
|
|
cursor->pitch,
|
|
data)
|
|
)
|
|
{
|
|
DEBUG_ERROR("Failed to update mouse shape");
|
|
lgmpClientMessageDone(queue);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (msg.udata & CURSOR_FLAG_POSITION)
|
|
{
|
|
bool valid = g_cursor.guest.valid;
|
|
g_cursor.guest.x = cursor->x;
|
|
g_cursor.guest.y = cursor->y;
|
|
g_cursor.guest.valid = true;
|
|
|
|
// if the state just became valid
|
|
if (valid != true && app_inputEnabled())
|
|
alignToGuest();
|
|
}
|
|
|
|
lgmpClientMessageDone(queue);
|
|
g_cursor.redraw = false;
|
|
|
|
g_state.lgr->on_mouse_event
|
|
(
|
|
g_state.lgrData,
|
|
g_cursor.guest.visible && g_cursor.draw,
|
|
g_cursor.guest.x,
|
|
g_cursor.guest.y
|
|
);
|
|
|
|
if (params.mouseRedraw)
|
|
lgSignalEvent(e_frame);
|
|
}
|
|
|
|
lgmpClientUnsubscribe(&queue);
|
|
return 0;
|
|
}
|
|
|
|
static int frameThread(void * unused)
|
|
{
|
|
struct DMAFrameInfo
|
|
{
|
|
KVMFRFrame * frame;
|
|
size_t dataSize;
|
|
int fd;
|
|
};
|
|
|
|
LGMP_STATUS status;
|
|
PLGMPClientQueue queue;
|
|
|
|
uint32_t formatVer = 0;
|
|
bool formatValid = false;
|
|
size_t dataSize;
|
|
LG_RendererFormat lgrFormat;
|
|
|
|
struct DMAFrameInfo dmaInfo[LGMP_Q_FRAME_LEN] = {0};
|
|
const bool useDMA =
|
|
params.allowDMA &&
|
|
ivshmemHasDMA(&g_state.shm) &&
|
|
g_state.lgr->supports &&
|
|
g_state.lgr->supports(g_state.lgrData, LG_SUPPORTS_DMABUF);
|
|
|
|
if (useDMA)
|
|
DEBUG_INFO("Using DMA buffer support");
|
|
|
|
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
|
|
lgWaitEvent(e_startup, TIMEOUT_INFINITE);
|
|
if (g_state.state != APP_STATE_RUNNING)
|
|
return 0;
|
|
|
|
// subscribe to the frame queue
|
|
while(g_state.state == APP_STATE_RUNNING)
|
|
{
|
|
status = lgmpClientSubscribe(g_state.lgmp, LGMP_Q_FRAME, &queue);
|
|
if (status == LGMP_OK)
|
|
break;
|
|
|
|
if (status == LGMP_ERR_NO_SUCH_QUEUE)
|
|
{
|
|
usleep(1000);
|
|
continue;
|
|
}
|
|
|
|
DEBUG_ERROR("lgmpClientSubscribe Failed: %s", lgmpStatusString(status));
|
|
g_state.state = APP_STATE_SHUTDOWN;
|
|
break;
|
|
}
|
|
|
|
while(g_state.state == APP_STATE_RUNNING && !g_state.stopVideo)
|
|
{
|
|
LGMPMessage msg;
|
|
if ((status = lgmpClientProcess(queue, &msg)) != LGMP_OK)
|
|
{
|
|
if (status == LGMP_ERR_QUEUE_EMPTY)
|
|
{
|
|
const struct timespec req =
|
|
{
|
|
.tv_sec = 0,
|
|
.tv_nsec = params.framePollInterval * 1000L
|
|
};
|
|
|
|
struct timespec rem;
|
|
while(nanosleep(&req, &rem) < 0)
|
|
if (errno != -EINTR)
|
|
{
|
|
DEBUG_ERROR("nanosleep failed");
|
|
break;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (status == LGMP_ERR_INVALID_SESSION)
|
|
g_state.state = APP_STATE_RESTART;
|
|
else
|
|
{
|
|
DEBUG_ERROR("lgmpClientProcess Failed: %s", lgmpStatusString(status));
|
|
g_state.state = APP_STATE_SHUTDOWN;
|
|
}
|
|
break;
|
|
}
|
|
|
|
KVMFRFrame * frame = (KVMFRFrame *)msg.mem;
|
|
struct DMAFrameInfo *dma = NULL;
|
|
|
|
if (!formatValid || frame->formatVer != formatVer)
|
|
{
|
|
// setup the renderer format with the frame format details
|
|
lgrFormat.type = frame->type;
|
|
lgrFormat.width = frame->width;
|
|
lgrFormat.height = frame->height;
|
|
lgrFormat.stride = frame->stride;
|
|
lgrFormat.pitch = frame->pitch;
|
|
|
|
bool error = false;
|
|
switch(frame->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_RGBA16F:
|
|
dataSize = lgrFormat.height * lgrFormat.pitch;
|
|
lgrFormat.bpp = 64;
|
|
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)
|
|
{
|
|
lgmpClientMessageDone(queue);
|
|
g_state.state = APP_STATE_SHUTDOWN;
|
|
break;
|
|
}
|
|
|
|
formatValid = true;
|
|
formatVer = frame->formatVer;
|
|
|
|
DEBUG_INFO("Format: %s %ux%u %u %u",
|
|
FrameTypeStr[frame->type],
|
|
frame->width, frame->height,
|
|
frame->stride, frame->pitch);
|
|
|
|
lgrFormat.rotate = params.winRotate;
|
|
|
|
if (!g_state.lgr->on_frame_format(g_state.lgrData, lgrFormat, useDMA))
|
|
{
|
|
DEBUG_ERROR("renderer failed to configure format");
|
|
g_state.state = APP_STATE_SHUTDOWN;
|
|
break;
|
|
}
|
|
|
|
g_state.srcSize.x = lgrFormat.width;
|
|
g_state.srcSize.y = lgrFormat.height;
|
|
g_state.haveSrcSize = true;
|
|
if (params.autoResize)
|
|
SDL_SetWindowSize(g_state.window, lgrFormat.width, lgrFormat.height);
|
|
|
|
g_cursor.guest.dpiScale = frame->mouseScalePercent;
|
|
updatePositionInfo();
|
|
}
|
|
|
|
if (useDMA)
|
|
{
|
|
/* find the existing dma buffer if it exists */
|
|
for(int i = 0; i < sizeof(dmaInfo) / sizeof(struct DMAFrameInfo); ++i)
|
|
{
|
|
if (dmaInfo[i].frame == frame)
|
|
{
|
|
dma = &dmaInfo[i];
|
|
/* if it's too small close it */
|
|
if (dma->dataSize < dataSize)
|
|
{
|
|
close(dma->fd);
|
|
dma->fd = -1;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* otherwise find a free buffer for use */
|
|
if (!dma)
|
|
for(int i = 0; i < sizeof(dmaInfo) / sizeof(struct DMAFrameInfo); ++i)
|
|
{
|
|
if (!dmaInfo[i].frame)
|
|
{
|
|
dma = &dmaInfo[i];
|
|
dma->frame = frame;
|
|
dma->fd = -1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* open the buffer */
|
|
if (dma->fd == -1)
|
|
{
|
|
const uintptr_t pos = (uintptr_t)msg.mem - (uintptr_t)g_state.shm.mem;
|
|
const uintptr_t offset = (uintptr_t)frame->offset + FrameBufferStructSize;
|
|
|
|
dma->dataSize = dataSize;
|
|
dma->fd = ivshmemGetDMABuf(&g_state.shm, pos + offset, dataSize);
|
|
if (dma->fd < 0)
|
|
{
|
|
DEBUG_ERROR("Failed to get the DMA buffer for the frame");
|
|
g_state.state = APP_STATE_SHUTDOWN;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
FrameBuffer * fb = (FrameBuffer *)(((uint8_t*)frame) + frame->offset);
|
|
if (!g_state.lgr->on_frame(g_state.lgrData, fb, useDMA ? dma->fd : -1))
|
|
{
|
|
lgmpClientMessageDone(queue);
|
|
DEBUG_ERROR("renderer on frame returned failure");
|
|
g_state.state = APP_STATE_SHUTDOWN;
|
|
break;
|
|
}
|
|
|
|
atomic_fetch_add_explicit(&g_state.frameCount, 1, memory_order_relaxed);
|
|
lgSignalEvent(e_frame);
|
|
lgmpClientMessageDone(queue);
|
|
}
|
|
|
|
lgmpClientUnsubscribe(&queue);
|
|
g_state.lgr->on_restart(g_state.lgrData);
|
|
|
|
if (useDMA)
|
|
{
|
|
for(int i = 0; i < sizeof(dmaInfo) / sizeof(struct DMAFrameInfo); ++i)
|
|
if (dmaInfo[i].fd >= 0)
|
|
close(dmaInfo[i].fd);
|
|
}
|
|
|
|
|
|
return 0;
|
|
}
|
|
|
|
int spiceThread(void * arg)
|
|
{
|
|
while(g_state.state != APP_STATE_SHUTDOWN)
|
|
if (!spice_process(1000))
|
|
{
|
|
if (g_state.state != APP_STATE_SHUTDOWN)
|
|
{
|
|
g_state.state = APP_STATE_SHUTDOWN;
|
|
DEBUG_ERROR("failed to process spice messages");
|
|
}
|
|
break;
|
|
}
|
|
|
|
g_state.state = APP_STATE_SHUTDOWN;
|
|
return 0;
|
|
}
|
|
|
|
static inline 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 app_clipboardRelease(void)
|
|
{
|
|
if (!params.clipboardToVM)
|
|
return;
|
|
|
|
spice_clipboard_release();
|
|
}
|
|
|
|
void app_clipboardNotify(const LG_ClipboardData type, size_t size)
|
|
{
|
|
if (!params.clipboardToVM)
|
|
return;
|
|
|
|
if (type == LG_CLIPBOARD_DATA_NONE)
|
|
{
|
|
spice_clipboard_release();
|
|
return;
|
|
}
|
|
|
|
g_state.cbType = clipboard_type_to_spice_type(type);
|
|
g_state.cbChunked = size > 0;
|
|
g_state.cbXfer = size;
|
|
|
|
spice_clipboard_grab(g_state.cbType);
|
|
|
|
if (size)
|
|
spice_clipboard_data_start(g_state.cbType, size);
|
|
}
|
|
|
|
void app_clipboardData(const LG_ClipboardData type, uint8_t * data, size_t size)
|
|
{
|
|
if (!params.clipboardToVM)
|
|
return;
|
|
|
|
if (g_state.cbChunked && size > g_state.cbXfer)
|
|
{
|
|
DEBUG_ERROR("refusing to send more then cbXfer bytes for chunked xfer");
|
|
size = g_state.cbXfer;
|
|
}
|
|
|
|
if (!g_state.cbChunked)
|
|
spice_clipboard_data_start(g_state.cbType, size);
|
|
|
|
spice_clipboard_data(g_state.cbType, data, (uint32_t)size);
|
|
g_state.cbXfer -= size;
|
|
}
|
|
|
|
void app_clipboardRequest(const LG_ClipboardReplyFn replyFn, void * opaque)
|
|
{
|
|
if (!params.clipboardToLocal)
|
|
return;
|
|
|
|
struct CBRequest * cbr = (struct CBRequest *)malloc(sizeof(struct CBRequest));
|
|
|
|
cbr->type = g_state.cbType;
|
|
cbr->replyFn = replyFn;
|
|
cbr->opaque = opaque;
|
|
ll_push(g_state.cbRequestList, cbr);
|
|
|
|
spice_clipboard_request(g_state.cbType);
|
|
}
|
|
|
|
void spiceClipboardNotice(const SpiceDataType type)
|
|
{
|
|
if (!params.clipboardToLocal)
|
|
return;
|
|
|
|
if (!g_state.cbAvailable)
|
|
return;
|
|
|
|
g_state.cbType = type;
|
|
g_state.ds->cbNotice(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(g_state.cbRequestList, (void **)&cbr))
|
|
{
|
|
cbr->replyFn(cbr->opaque, spice_type_to_clipboard_type(type), buffer, size);
|
|
free(cbr);
|
|
}
|
|
}
|
|
|
|
void spiceClipboardRelease(void)
|
|
{
|
|
if (!params.clipboardToLocal)
|
|
return;
|
|
|
|
if (g_state.cbAvailable)
|
|
g_state.ds->cbRelease();
|
|
}
|
|
|
|
void spiceClipboardRequest(const SpiceDataType type)
|
|
{
|
|
if (!params.clipboardToVM)
|
|
return;
|
|
|
|
if (g_state.cbAvailable)
|
|
g_state.ds->cbRequest(spice_type_to_clipboard_type(type));
|
|
}
|
|
|
|
static void warpMouse(int x, int y, bool exiting)
|
|
{
|
|
if (g_cursor.warpState == WARP_STATE_OFF)
|
|
return;
|
|
|
|
if (exiting)
|
|
g_cursor.warpState = WARP_STATE_OFF;
|
|
|
|
if (g_cursor.pos.x == x && g_cursor.pos.y == y)
|
|
return;
|
|
|
|
g_state.ds->warpMouse(x, y, exiting);
|
|
}
|
|
|
|
static bool isValidCursorLocation(int x, int y)
|
|
{
|
|
const int displays = SDL_GetNumVideoDisplays();
|
|
for(int i = 0; i < displays; ++i)
|
|
{
|
|
SDL_Rect r;
|
|
SDL_GetDisplayBounds(i, &r);
|
|
if ((x >= r.x && x < r.x + r.w) &&
|
|
(y >= r.y && y < r.y + r.h))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void cursorToInt(double ex, double ey, int *x, int *y)
|
|
{
|
|
/* only smooth if enabled and not using raw mode */
|
|
if (params.mouseSmoothing && !(g_cursor.grab && params.rawMouse))
|
|
{
|
|
static struct DoublePoint last = { 0 };
|
|
ex = last.x = (last.x + ex) / 2.0;
|
|
ey = last.y = (last.y + ey) / 2.0;
|
|
}
|
|
|
|
/* convert to int accumulating the fractional error */
|
|
ex += g_cursor.acc.x;
|
|
ey += g_cursor.acc.y;
|
|
g_cursor.acc.x = modf(ex, &ex);
|
|
g_cursor.acc.y = modf(ey, &ey);
|
|
|
|
*x = (int)ex;
|
|
*y = (int)ey;
|
|
}
|
|
|
|
static void setCursorInView(bool enable)
|
|
{
|
|
// if we don't have focus don't do anything
|
|
if (enable && !g_state.focused)
|
|
return;
|
|
|
|
/* if the display server does not support warp, then we can not operate in
|
|
* always relative mode and we should not grab the pointer */
|
|
bool warpSupport = true;
|
|
app_getProp(LG_DS_WARP_SUPPORT, &warpSupport);
|
|
|
|
g_cursor.inView = enable;
|
|
g_cursor.draw = params.alwaysShowCursor ? true : enable;
|
|
g_cursor.redraw = true;
|
|
|
|
g_cursor.warpState = enable ? WARP_STATE_ON : WARP_STATE_OFF;
|
|
|
|
if (enable)
|
|
{
|
|
if (params.hideMouse)
|
|
SDL_ShowCursor(SDL_DISABLE);
|
|
|
|
if (warpSupport)
|
|
g_state.ds->grabPointer();
|
|
}
|
|
else
|
|
{
|
|
if (params.hideMouse)
|
|
SDL_ShowCursor(SDL_ENABLE);
|
|
|
|
if (warpSupport)
|
|
g_state.ds->ungrabPointer();
|
|
}
|
|
}
|
|
|
|
void app_handleMouseGrabbed(double ex, double ey)
|
|
{
|
|
int x, y;
|
|
|
|
if (params.rawMouse && !g_cursor.useScale)
|
|
{
|
|
/* raw unscaled input are always round numbers */
|
|
x = floor(ex);
|
|
y = floor(ey);
|
|
}
|
|
else
|
|
{
|
|
/* apply sensitivity */
|
|
ex = (ex / 10.0) * (g_cursor.sens + 10);
|
|
ey = (ey / 10.0) * (g_cursor.sens + 10);
|
|
cursorToInt(ex, ey, &x, &y);
|
|
}
|
|
|
|
if (x == 0 && y == 0)
|
|
return;
|
|
|
|
if (!spice_mouse_motion(x, y))
|
|
DEBUG_ERROR("failed to send mouse motion message");
|
|
}
|
|
|
|
static void guestCurToLocal(struct DoublePoint *local)
|
|
{
|
|
local->x = (g_cursor.guest.x + g_cursor.guest.hx) / g_cursor.scale.x;
|
|
local->y = (g_cursor.guest.y + g_cursor.guest.hy) / g_cursor.scale.y;
|
|
}
|
|
|
|
void app_handleMouseNormal(double ex, double ey)
|
|
{
|
|
// prevent cursor handling outside of capture if the position is not known
|
|
if (!g_cursor.guest.valid)
|
|
return;
|
|
|
|
/* scale the movement to the guest */
|
|
if (g_cursor.useScale && params.scaleMouseInput)
|
|
{
|
|
ex *= g_cursor.scale.x / g_cursor.dpiScale;
|
|
ey *= g_cursor.scale.y / g_cursor.dpiScale;
|
|
}
|
|
|
|
bool testExit = true;
|
|
|
|
/* if the cursor was outside the viewport, check if it moved in */
|
|
if (!g_cursor.inView)
|
|
{
|
|
const bool inView =
|
|
g_cursor.pos.x >= g_state.dstRect.x &&
|
|
g_cursor.pos.x < g_state.dstRect.x + g_state.dstRect.w &&
|
|
g_cursor.pos.y >= g_state.dstRect.y &&
|
|
g_cursor.pos.y < g_state.dstRect.y + g_state.dstRect.h;
|
|
|
|
if (inView)
|
|
{
|
|
setCursorInView(true);
|
|
|
|
struct DoublePoint guest =
|
|
{
|
|
.x = (g_cursor.pos.x - g_state.dstRect.x) * g_cursor.scale.x,
|
|
.y = (g_cursor.pos.y - g_state.dstRect.y) * g_cursor.scale.y
|
|
};
|
|
|
|
/* add the difference to the offset */
|
|
ex += guest.x - (g_cursor.guest.x + g_cursor.guest.hx);
|
|
ey += guest.y - (g_cursor.guest.y + g_cursor.guest.hy);
|
|
|
|
/* don't test for an exit as we just entered, we can get into a enter/exit
|
|
* loop otherwise */
|
|
testExit = false;
|
|
}
|
|
else
|
|
{
|
|
/* nothing to do if the cursor is not in the guest window */
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* if we are in "autoCapture" and the delta was large don't test for exit */
|
|
if (params.autoCapture &&
|
|
(fabs(ex) > 100.0 / g_cursor.scale.x || fabs(ey) > 100.0 / g_cursor.scale.y))
|
|
testExit = false;
|
|
|
|
/* translate the guests position to our coordinate space */
|
|
struct DoublePoint local;
|
|
guestCurToLocal(&local);
|
|
|
|
/* check if the move would push the cursor outside the guest's viewport */
|
|
if (testExit && (
|
|
local.x + ex < 0.0 ||
|
|
local.y + ey < 0.0 ||
|
|
local.x + ex >= g_state.dstRect.w ||
|
|
local.y + ey >= g_state.dstRect.h))
|
|
{
|
|
local.x += ex;
|
|
local.y += ey;
|
|
const int tx = ((local.x <= 0.0) ? floor(local.x) : ceil(local.x)) +
|
|
g_state.dstRect.x;
|
|
const int ty = ((local.y <= 0.0) ? floor(local.y) : ceil(local.y)) +
|
|
g_state.dstRect.y;
|
|
|
|
if (isValidCursorLocation(
|
|
g_state.windowPos.x + g_state.border.x + tx,
|
|
g_state.windowPos.y + g_state.border.y + ty))
|
|
{
|
|
setCursorInView(false);
|
|
|
|
/* preempt the window leave flag if the warp will leave our window */
|
|
if (tx < 0 || ty < 0 || tx > g_state.windowW || ty > g_state.windowH)
|
|
g_cursor.inWindow = false;
|
|
|
|
/* ungrab the pointer and move the local cursor to the exit point */
|
|
g_state.ds->ungrabPointer();
|
|
warpMouse(tx, ty, true);
|
|
return;
|
|
}
|
|
}
|
|
|
|
int x, y;
|
|
cursorToInt(ex, ey, &x, &y);
|
|
|
|
if (x == 0 && y == 0)
|
|
return;
|
|
|
|
if (params.autoCapture)
|
|
{
|
|
g_cursor.delta.x += x;
|
|
g_cursor.delta.y += y;
|
|
|
|
if (fabs(g_cursor.delta.x) > 50.0 || fabs(g_cursor.delta.y) > 50.0)
|
|
{
|
|
g_cursor.delta.x = 0;
|
|
g_cursor.delta.y = 0;
|
|
warpMouse(g_state.windowCX, g_state.windowCY, false);
|
|
}
|
|
|
|
g_cursor.guest.x = g_state.srcSize.x / 2;
|
|
g_cursor.guest.y = g_state.srcSize.y / 2;
|
|
}
|
|
else
|
|
{
|
|
/* assume the mouse will move to the location we attempt to move it to so we
|
|
* avoid warp out of window issues. The cursorThread will correct this if
|
|
* wrong after the movement has ocurred on the guest */
|
|
g_cursor.guest.x += x;
|
|
g_cursor.guest.y += y;
|
|
}
|
|
|
|
if (!spice_mouse_motion(x, y))
|
|
DEBUG_ERROR("failed to send mouse motion message");
|
|
}
|
|
|
|
|
|
// On some display servers normal cursor logic does not work due to the lack of
|
|
// cursor warp support. Instead, we attempt a best-effort emulation which works
|
|
// with a 1:1 mouse movement patch applied in the guest. For anything fancy, use
|
|
// capture mode.
|
|
void app_handleMouseBasic(double ex, double ey)
|
|
{
|
|
/* if we don't have the current cursor pos just send cursor movements */
|
|
if (!g_cursor.guest.valid)
|
|
{
|
|
if (g_cursor.grab)
|
|
app_handleMouseGrabbed(ex, ey);
|
|
return;
|
|
}
|
|
|
|
const bool inView =
|
|
g_cursor.pos.x >= g_state.dstRect.x &&
|
|
g_cursor.pos.x < g_state.dstRect.x + g_state.dstRect.w &&
|
|
g_cursor.pos.y >= g_state.dstRect.y &&
|
|
g_cursor.pos.y < g_state.dstRect.y + g_state.dstRect.h;
|
|
|
|
if (params.hideMouse && inView != g_cursor.inView)
|
|
|
|
if (inView != g_cursor.inView)
|
|
{
|
|
g_cursor.inView = inView;
|
|
setCursorInView(inView);
|
|
}
|
|
|
|
if (g_cursor.guest.dpiScale == 0)
|
|
return;
|
|
|
|
/* translate the guests position to our coordinate space */
|
|
struct DoublePoint local;
|
|
guestCurToLocal(&local);
|
|
|
|
double lx = (g_cursor.pos.x - local.x) / g_cursor.dpiScale;
|
|
double ly = (g_cursor.pos.y - local.y) / g_cursor.dpiScale;
|
|
|
|
int x, y;
|
|
cursorToInt(lx, ly, &x, &y);
|
|
|
|
g_cursor.guest.x += x;
|
|
g_cursor.guest.y += y;
|
|
|
|
if (!spice_mouse_motion(x, y))
|
|
DEBUG_ERROR("failed to send mouse motion message");
|
|
}
|
|
|
|
void app_updateWindowPos(int x, int y)
|
|
{
|
|
g_state.windowPos.x = x;
|
|
g_state.windowPos.y = y;
|
|
}
|
|
|
|
void app_handleResizeEvent(int w, int h)
|
|
{
|
|
SDL_GetWindowBordersSize(g_state.window,
|
|
&g_state.border.y,
|
|
&g_state.border.x,
|
|
&g_state.border.h,
|
|
&g_state.border.w
|
|
);
|
|
|
|
g_state.windowW = w;
|
|
g_state.windowH = h;
|
|
g_state.windowCX = w / 2;
|
|
g_state.windowCY = h / 2;
|
|
updatePositionInfo();
|
|
|
|
if (app_inputEnabled())
|
|
{
|
|
/* if the window is moved/resized causing a loss of focus while grabbed, it
|
|
* makes it impossible to re-focus the window, so we quietly re-enter
|
|
* capture if we were already in it */
|
|
if (g_cursor.grab)
|
|
{
|
|
setGrabQuiet(false);
|
|
setGrabQuiet(true);
|
|
}
|
|
alignToGuest();
|
|
}
|
|
}
|
|
|
|
void app_handleWindowLeave(void)
|
|
{
|
|
g_cursor.inWindow = false;
|
|
g_cursor.inView = false;
|
|
|
|
if (!app_inputEnabled())
|
|
return;
|
|
|
|
if (!params.alwaysShowCursor)
|
|
g_cursor.draw = false;
|
|
g_cursor.redraw = true;
|
|
}
|
|
|
|
void app_handleWindowEnter(void)
|
|
{
|
|
g_cursor.inWindow = true;
|
|
if (!app_inputEnabled())
|
|
return;
|
|
}
|
|
|
|
static void setGrab(bool enable)
|
|
{
|
|
setGrabQuiet(enable);
|
|
|
|
app_alert(
|
|
g_cursor.grab ? LG_ALERT_SUCCESS : LG_ALERT_WARNING,
|
|
g_cursor.grab ? "Capture Enabled" : "Capture Disabled"
|
|
);
|
|
}
|
|
|
|
static void setGrabQuiet(bool enable)
|
|
{
|
|
/* we always do this so that at init the cursor is in the right state */
|
|
if (params.captureInputOnly && params.hideMouse)
|
|
SDL_ShowCursor(enable ? SDL_DISABLE : SDL_ENABLE);
|
|
|
|
if (g_cursor.grab == enable)
|
|
return;
|
|
|
|
g_cursor.grab = enable;
|
|
g_cursor.acc.x = 0.0;
|
|
g_cursor.acc.y = 0.0;
|
|
|
|
/* if the display server does not support warp we need to grab the pointer
|
|
* here instead of in the move handler */
|
|
bool warpSupport = true;
|
|
app_getProp(LG_DS_WARP_SUPPORT, &warpSupport);
|
|
|
|
if (enable)
|
|
{
|
|
if (!g_cursor.inView)
|
|
setCursorInView(true);
|
|
|
|
if (params.grabKeyboard)
|
|
g_state.ds->grabKeyboard();
|
|
|
|
if (!warpSupport)
|
|
g_state.ds->grabPointer();
|
|
|
|
}
|
|
else
|
|
{
|
|
if (params.grabKeyboard)
|
|
{
|
|
if (!g_state.focused || !params.grabKeyboardOnFocus)
|
|
g_state.ds->ungrabKeyboard();
|
|
}
|
|
|
|
if (!warpSupport)
|
|
g_state.ds->ungrabPointer();
|
|
}
|
|
|
|
// if exiting capture when input on capture only, we want to show the cursor
|
|
if (!enable && (params.captureInputOnly || !params.hideMouse))
|
|
alignToGuest();
|
|
|
|
if (g_cursor.grab)
|
|
g_cursor.inView = true;
|
|
}
|
|
|
|
int eventFilter(void * userdata, SDL_Event * event)
|
|
{
|
|
if (g_state.ds->eventFilter(event))
|
|
return 0;
|
|
|
|
if (event->type == e_SDLEvent)
|
|
{
|
|
switch(event->user.code)
|
|
{
|
|
case LG_EVENT_ALIGN_TO_GUEST:
|
|
{
|
|
if (!g_cursor.guest.valid || !g_state.focused)
|
|
break;
|
|
|
|
struct DoublePoint local;
|
|
guestCurToLocal(&local);
|
|
warpMouse(round(local.x), round(local.y), false);
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
switch(event->type)
|
|
{
|
|
case SDL_QUIT:
|
|
{
|
|
if (!params.ignoreQuit)
|
|
{
|
|
DEBUG_INFO("Quit event received, exiting...");
|
|
g_state.state = APP_STATE_SHUTDOWN;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
case SDL_WINDOWEVENT:
|
|
{
|
|
switch(event->window.event)
|
|
{
|
|
case SDL_WINDOWEVENT_ENTER:
|
|
app_handleWindowEnter();
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT_LEAVE:
|
|
app_handleWindowLeave();
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT_FOCUS_GAINED:
|
|
g_state.focused = true;
|
|
|
|
if (!app_inputEnabled())
|
|
break;
|
|
if (params.grabKeyboardOnFocus)
|
|
g_state.ds->grabKeyboard();
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT_FOCUS_LOST:
|
|
g_state.focused = false;
|
|
|
|
if (!app_inputEnabled())
|
|
break;
|
|
if (params.grabKeyboardOnFocus)
|
|
g_state.ds->ungrabKeyboard();
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
|
case SDL_WINDOWEVENT_RESIZED:
|
|
app_handleResizeEvent(event->window.data1, event->window.data2);
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT_MOVED:
|
|
app_updateWindowPos(event->window.data1, event->window.data2);
|
|
break;
|
|
|
|
case SDL_WINDOWEVENT_CLOSE:
|
|
if (!params.ignoreQuit || !g_cursor.inView)
|
|
g_state.state = APP_STATE_SHUTDOWN;
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
case SDL_KEYDOWN:
|
|
{
|
|
SDL_Scancode sc = event->key.keysym.scancode;
|
|
if (sc == params.escapeKey && !g_state.escapeActive)
|
|
{
|
|
g_state.escapeActive = true;
|
|
g_state.escapeAction = -1;
|
|
break;
|
|
}
|
|
|
|
if (g_state.escapeActive)
|
|
{
|
|
g_state.escapeAction = sc;
|
|
break;
|
|
}
|
|
|
|
if (!app_inputEnabled())
|
|
break;
|
|
|
|
if (params.ignoreWindowsKeys &&
|
|
(sc == SDL_SCANCODE_LGUI || sc == SDL_SCANCODE_RGUI))
|
|
break;
|
|
|
|
|
|
uint32_t scancode = mapScancode(sc);
|
|
if (scancode == 0)
|
|
break;
|
|
|
|
if (!g_state.keyDown[sc])
|
|
{
|
|
if (spice_key_down(scancode))
|
|
g_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 (g_state.escapeActive)
|
|
{
|
|
if (g_state.escapeAction == -1)
|
|
{
|
|
if (params.useSpiceInput)
|
|
setGrab(!g_cursor.grab);
|
|
}
|
|
else
|
|
{
|
|
KeybindHandle handle = g_state.bindings[sc];
|
|
if (handle)
|
|
{
|
|
handle->callback(sc, handle->opaque);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (sc == params.escapeKey)
|
|
g_state.escapeActive = false;
|
|
}
|
|
|
|
if (!app_inputEnabled())
|
|
break;
|
|
|
|
// avoid sending key up events when we didn't send a down
|
|
if (!g_state.keyDown[sc])
|
|
break;
|
|
|
|
if (params.ignoreWindowsKeys &&
|
|
(sc == SDL_SCANCODE_LGUI || sc == SDL_SCANCODE_RGUI))
|
|
break;
|
|
|
|
uint32_t scancode = mapScancode(sc);
|
|
if (scancode == 0)
|
|
break;
|
|
|
|
if (spice_key_up(scancode))
|
|
g_state.keyDown[sc] = false;
|
|
else
|
|
{
|
|
DEBUG_ERROR("SDL_KEYUP: failed to send message");
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SDL_MOUSEWHEEL:
|
|
if (!app_inputEnabled() || !g_cursor.inView)
|
|
break;
|
|
|
|
if (
|
|
!spice_mouse_press (event->wheel.y > 0 ? 4 : 5) ||
|
|
!spice_mouse_release(event->wheel.y > 0 ? 4 : 5)
|
|
)
|
|
{
|
|
DEBUG_ERROR("SDL_MOUSEWHEEL: failed to send messages");
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case SDL_MOUSEBUTTONDOWN:
|
|
{
|
|
if (!app_inputEnabled() || !g_cursor.inView)
|
|
break;
|
|
|
|
int button = event->button.button;
|
|
if (button > 3)
|
|
button += 2;
|
|
|
|
if (!spice_mouse_press(button))
|
|
{
|
|
DEBUG_ERROR("SDL_MOUSEBUTTONDOWN: failed to send message");
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SDL_MOUSEBUTTONUP:
|
|
{
|
|
if (!app_inputEnabled() || !g_cursor.inView)
|
|
break;
|
|
|
|
int button = event->button.button;
|
|
if (button > 3)
|
|
button += 2;
|
|
|
|
if (!spice_mouse_release(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...");
|
|
g_state.state = APP_STATE_SHUTDOWN;
|
|
break;
|
|
}
|
|
}
|
|
|
|
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
|
|
g_state.lgrData = NULL;
|
|
if (!r->create(&g_state.lgrData, lgrParams))
|
|
return false;
|
|
|
|
// initialize the renderer
|
|
if (!r->initialize(g_state.lgrData, sdlFlags))
|
|
{
|
|
r->deinitialize(g_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(g_state.window, params.fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP);
|
|
params.fullscreen = !params.fullscreen;
|
|
}
|
|
|
|
static void toggle_video(SDL_Scancode key, void * opaque)
|
|
{
|
|
g_state.stopVideo = !g_state.stopVideo;
|
|
app_alert(
|
|
LG_ALERT_INFO,
|
|
g_state.stopVideo ? "Video Stream Disabled" : "Video Stream Enabled"
|
|
);
|
|
|
|
if (!g_state.stopVideo)
|
|
{
|
|
if (t_frame)
|
|
{
|
|
lgJoinThread(t_frame, NULL);
|
|
t_frame = NULL;
|
|
}
|
|
|
|
if (!lgCreateThread("frameThread", frameThread, NULL, &t_frame))
|
|
DEBUG_ERROR("frame create thread failed");
|
|
}
|
|
}
|
|
|
|
static void toggle_input(SDL_Scancode key, void * opaque)
|
|
{
|
|
g_state.ignoreInput = !g_state.ignoreInput;
|
|
app_alert(
|
|
LG_ALERT_INFO,
|
|
g_state.ignoreInput ? "Input Disabled" : "Input Enabled"
|
|
);
|
|
}
|
|
|
|
static void quit(SDL_Scancode key, void * opaque)
|
|
{
|
|
g_state.state = APP_STATE_SHUTDOWN;
|
|
}
|
|
|
|
static void mouse_sens_inc(SDL_Scancode key, void * opaque)
|
|
{
|
|
char * msg;
|
|
if (g_cursor.sens < 9)
|
|
++g_cursor.sens;
|
|
|
|
alloc_sprintf(&msg, "Sensitivity: %s%d", g_cursor.sens > 0 ? "+" : "", g_cursor.sens);
|
|
app_alert(
|
|
LG_ALERT_INFO,
|
|
msg
|
|
);
|
|
free(msg);
|
|
}
|
|
|
|
static void mouse_sens_dec(SDL_Scancode key, void * opaque)
|
|
{
|
|
char * msg;
|
|
|
|
if (g_cursor.sens > -9)
|
|
--g_cursor.sens;
|
|
|
|
alloc_sprintf(&msg, "Sensitivity: %s%d", g_cursor.sens > 0 ? "+" : "", g_cursor.sens);
|
|
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 key_passthrough(SDL_Scancode key, void * opaque)
|
|
{
|
|
const uint32_t sc = mapScancode(key);
|
|
spice_key_down(sc);
|
|
spice_key_up (sc);
|
|
}
|
|
|
|
static void register_key_binds(void)
|
|
{
|
|
g_state.kbFS = app_register_keybind(SDL_SCANCODE_F , toggle_fullscreen, NULL);
|
|
g_state.kbVideo = app_register_keybind(SDL_SCANCODE_V , toggle_video , NULL);
|
|
g_state.kbInput = app_register_keybind(SDL_SCANCODE_I , toggle_input , NULL);
|
|
g_state.kbQuit = app_register_keybind(SDL_SCANCODE_Q , quit , NULL);
|
|
g_state.kbMouseSensInc = app_register_keybind(SDL_SCANCODE_INSERT, mouse_sens_inc , NULL);
|
|
g_state.kbMouseSensDec = app_register_keybind(SDL_SCANCODE_DELETE, mouse_sens_dec , NULL);
|
|
|
|
g_state.kbCtrlAltFn[0 ] = app_register_keybind(SDL_SCANCODE_F1 , ctrl_alt_fn, NULL);
|
|
g_state.kbCtrlAltFn[1 ] = app_register_keybind(SDL_SCANCODE_F2 , ctrl_alt_fn, NULL);
|
|
g_state.kbCtrlAltFn[2 ] = app_register_keybind(SDL_SCANCODE_F3 , ctrl_alt_fn, NULL);
|
|
g_state.kbCtrlAltFn[3 ] = app_register_keybind(SDL_SCANCODE_F4 , ctrl_alt_fn, NULL);
|
|
g_state.kbCtrlAltFn[4 ] = app_register_keybind(SDL_SCANCODE_F5 , ctrl_alt_fn, NULL);
|
|
g_state.kbCtrlAltFn[5 ] = app_register_keybind(SDL_SCANCODE_F6 , ctrl_alt_fn, NULL);
|
|
g_state.kbCtrlAltFn[6 ] = app_register_keybind(SDL_SCANCODE_F7 , ctrl_alt_fn, NULL);
|
|
g_state.kbCtrlAltFn[7 ] = app_register_keybind(SDL_SCANCODE_F8 , ctrl_alt_fn, NULL);
|
|
g_state.kbCtrlAltFn[8 ] = app_register_keybind(SDL_SCANCODE_F9 , ctrl_alt_fn, NULL);
|
|
g_state.kbCtrlAltFn[9 ] = app_register_keybind(SDL_SCANCODE_F10, ctrl_alt_fn, NULL);
|
|
g_state.kbCtrlAltFn[10] = app_register_keybind(SDL_SCANCODE_F11, ctrl_alt_fn, NULL);
|
|
g_state.kbCtrlAltFn[11] = app_register_keybind(SDL_SCANCODE_F12, ctrl_alt_fn, NULL);
|
|
|
|
g_state.kbPass[0] = app_register_keybind(SDL_SCANCODE_LGUI, key_passthrough, NULL);
|
|
g_state.kbPass[1] = app_register_keybind(SDL_SCANCODE_RGUI, key_passthrough, NULL);
|
|
}
|
|
|
|
static void release_key_binds(void)
|
|
{
|
|
app_release_keybind(&g_state.kbFS );
|
|
app_release_keybind(&g_state.kbVideo);
|
|
app_release_keybind(&g_state.kbInput);
|
|
app_release_keybind(&g_state.kbQuit );
|
|
app_release_keybind(&g_state.kbMouseSensInc);
|
|
app_release_keybind(&g_state.kbMouseSensDec);
|
|
for(int i = 0; i < 12; ++i)
|
|
app_release_keybind(&g_state.kbCtrlAltFn[i]);
|
|
for(int i = 0; i < 2; ++i)
|
|
app_release_keybind(&g_state.kbPass[i]);
|
|
}
|
|
|
|
static void initSDLCursor(void)
|
|
{
|
|
const uint8_t data[4] = {0xf, 0x9, 0x9, 0xf};
|
|
const uint8_t mask[4] = {0xf, 0xf, 0xf, 0xf};
|
|
cursor = SDL_CreateCursor(data, mask, 8, 4, 4, 0);
|
|
SDL_SetCursor(cursor);
|
|
}
|
|
|
|
static int lg_run(void)
|
|
{
|
|
memset(&g_state, 0, sizeof(g_state));
|
|
|
|
lgInit();
|
|
|
|
g_cursor.sens = params.mouseSens;
|
|
if (g_cursor.sens < -9) g_cursor.sens = -9;
|
|
else if (g_cursor.sens > 9) g_cursor.sens = 9;
|
|
|
|
|
|
// try to early detect the platform
|
|
SDL_SYSWM_TYPE subsystem = SDL_SYSWM_UNKNOWN;
|
|
if (getenv("WAYLAND_DISPLAY")) subsystem = SDL_SYSWM_WAYLAND;
|
|
else if (getenv("DISPLAY" )) subsystem = SDL_SYSWM_X11;
|
|
else
|
|
DEBUG_WARN("Unknown subsystem, falling back to SDL default");
|
|
|
|
// search for the best displayserver ops to use
|
|
for(int i = 0; i < LG_DISPLAYSERVER_COUNT; ++i)
|
|
if (LG_DisplayServers[i]->subsystem == subsystem)
|
|
{
|
|
g_state.ds = LG_DisplayServers[i];
|
|
break;
|
|
}
|
|
|
|
assert(g_state.ds);
|
|
|
|
// set any null methods to the fallback
|
|
#define SET_FALLBACK(x) \
|
|
if (!g_state.ds->x) g_state.ds->x = LG_DisplayServers[0]->x;
|
|
SET_FALLBACK(earlyInit);
|
|
SET_FALLBACK(getProp);
|
|
SET_FALLBACK(init);
|
|
SET_FALLBACK(startup);
|
|
SET_FALLBACK(shutdown);
|
|
SET_FALLBACK(free);
|
|
SET_FALLBACK(eventFilter);
|
|
SET_FALLBACK(grabPointer);
|
|
SET_FALLBACK(ungrabKeyboard);
|
|
SET_FALLBACK(warpMouse);
|
|
SET_FALLBACK(cbInit);
|
|
SET_FALLBACK(cbNotice);
|
|
SET_FALLBACK(cbRelease);
|
|
SET_FALLBACK(cbRequest);
|
|
#undef SET_FALLBACK
|
|
|
|
// init the subsystem
|
|
if (!g_state.ds->earlyInit())
|
|
{
|
|
DEBUG_ERROR("Subsystem early init failed");
|
|
return -1;
|
|
}
|
|
|
|
if (!params.noScreensaver)
|
|
SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1");
|
|
|
|
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);
|
|
|
|
// try map the shared memory
|
|
if (!ivshmemOpen(&g_state.shm))
|
|
{
|
|
DEBUG_ERROR("Failed to map memory");
|
|
return -1;
|
|
}
|
|
|
|
// try to connect to the spice server
|
|
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 -1;
|
|
}
|
|
|
|
while(g_state.state != APP_STATE_SHUTDOWN && !spice_ready())
|
|
if (!spice_process(1000))
|
|
{
|
|
g_state.state = APP_STATE_SHUTDOWN;
|
|
DEBUG_ERROR("Failed to process spice messages");
|
|
return -1;
|
|
}
|
|
|
|
spice_mouse_mode(true);
|
|
if (!lgCreateThread("spiceThread", spiceThread, NULL, &t_spice))
|
|
{
|
|
DEBUG_ERROR("spice create thread failed");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// select and init a renderer
|
|
LG_RendererParams lgrParams;
|
|
lgrParams.showFPS = params.showFPS;
|
|
lgrParams.quickSplash = params.quickSplash;
|
|
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;
|
|
}
|
|
g_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))
|
|
{
|
|
g_state.lgr = LG_Renderers[i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!g_state.lgr)
|
|
{
|
|
DEBUG_INFO("Unable to find a suitable renderer");
|
|
return -1;
|
|
}
|
|
|
|
// all our ducks are in a line, create the window
|
|
g_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.allowResize ? SDL_WINDOW_RESIZABLE : 0) |
|
|
(params.borderless ? SDL_WINDOW_BORDERLESS : 0) |
|
|
(params.maximize ? SDL_WINDOW_MAXIMIZED : 0) |
|
|
sdlFlags
|
|
)
|
|
);
|
|
|
|
if (g_state.window == NULL)
|
|
{
|
|
DEBUG_ERROR("Could not create an SDL window: %s\n", SDL_GetError());
|
|
return 1;
|
|
}
|
|
|
|
SDL_VERSION(&g_state.wminfo.version);
|
|
if (!SDL_GetWindowWMInfo(g_state.window, &g_state.wminfo))
|
|
{
|
|
DEBUG_ERROR("Could not get SDL window information %s", SDL_GetError());
|
|
return -1;
|
|
}
|
|
|
|
g_state.ds->init(&g_state.wminfo);
|
|
|
|
SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS,
|
|
params.minimizeOnFocusLoss ? "1" : "0");
|
|
|
|
if (params.fullscreen)
|
|
SDL_SetWindowFullscreen(g_state.window, SDL_WINDOW_FULLSCREEN_DESKTOP);
|
|
|
|
if (!params.center)
|
|
SDL_SetWindowPosition(g_state.window, params.x, params.y);
|
|
|
|
// ensure the initial window size is stored in the state
|
|
SDL_GetWindowSize(g_state.window, &g_state.windowW, &g_state.windowH);
|
|
|
|
// ensure renderer viewport is aware of the current window size
|
|
updatePositionInfo();
|
|
|
|
if (params.fpsMin <= 0)
|
|
{
|
|
// default 30 fps
|
|
g_state.frameTime = 1000000000ULL / 30ULL;
|
|
}
|
|
else
|
|
{
|
|
DEBUG_INFO("Using the FPS minimum from args: %d", params.fpsMin);
|
|
g_state.frameTime = 1000000000ULL / (unsigned long long)params.fpsMin;
|
|
}
|
|
|
|
// create our custom event
|
|
e_SDLEvent = SDL_RegisterEvents(1);
|
|
|
|
register_key_binds();
|
|
|
|
initSDLCursor();
|
|
if (params.hideMouse)
|
|
SDL_ShowCursor(SDL_DISABLE);
|
|
|
|
// setup the startup condition
|
|
if (!(e_startup = lgCreateEvent(false, 0)))
|
|
{
|
|
DEBUG_ERROR("failed to create the startup event");
|
|
return -1;
|
|
}
|
|
|
|
// setup the new frame event
|
|
if (!(e_frame = lgCreateEvent(true, 0)))
|
|
{
|
|
DEBUG_ERROR("failed to create the frame event");
|
|
return -1;
|
|
}
|
|
|
|
// start the renderThread so we don't just display junk
|
|
if (!lgCreateThread("renderThread", renderThread, NULL, &t_render))
|
|
{
|
|
DEBUG_ERROR("render create thread failed");
|
|
return -1;
|
|
}
|
|
|
|
// ensure mouse acceleration is identical in server mode
|
|
SDL_SetHintWithPriority(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, "1", SDL_HINT_OVERRIDE);
|
|
SDL_SetEventFilter(eventFilter, NULL);
|
|
|
|
// wait for startup to complete so that any error messages below are output at
|
|
// the end of the output
|
|
lgWaitEvent(e_startup, TIMEOUT_INFINITE);
|
|
|
|
g_state.ds->startup();
|
|
g_state.cbAvailable = g_state.ds->cbInit && g_state.ds->cbInit();
|
|
if (g_state.cbAvailable)
|
|
g_state.cbRequestList = ll_new();
|
|
|
|
LGMP_STATUS status;
|
|
|
|
while(g_state.state == APP_STATE_RUNNING)
|
|
{
|
|
if ((status = lgmpClientInit(g_state.shm.mem, g_state.shm.size, &g_state.lgmp)) == LGMP_OK)
|
|
break;
|
|
|
|
DEBUG_ERROR("lgmpClientInit Failed: %s", lgmpStatusString(status));
|
|
return -1;
|
|
}
|
|
|
|
/* this short timeout is to allow the LGMP host to update the timestamp before
|
|
* we start checking for a valid session */
|
|
SDL_WaitEventTimeout(NULL, 200);
|
|
|
|
if (params.captureOnStart)
|
|
setGrab(true);
|
|
|
|
uint32_t udataSize;
|
|
KVMFR *udata;
|
|
int waitCount = 0;
|
|
|
|
restart:
|
|
while(g_state.state == APP_STATE_RUNNING)
|
|
{
|
|
if ((status = lgmpClientSessionInit(g_state.lgmp, &udataSize, (uint8_t **)&udata)) == LGMP_OK)
|
|
break;
|
|
|
|
if (status != LGMP_ERR_INVALID_SESSION && status != LGMP_ERR_INVALID_MAGIC)
|
|
{
|
|
DEBUG_ERROR("lgmpClientSessionInit Failed: %s", lgmpStatusString(status));
|
|
return -1;
|
|
}
|
|
|
|
if (waitCount++ == 0)
|
|
{
|
|
DEBUG_BREAK();
|
|
DEBUG_INFO("The host application seems to not be running");
|
|
DEBUG_INFO("Waiting for the host application to start...");
|
|
}
|
|
|
|
if (waitCount == 30)
|
|
{
|
|
DEBUG_BREAK();
|
|
DEBUG_INFO("Please check the host application is running and is the correct version");
|
|
DEBUG_INFO("Check the host log in your guest at %%TEMP%%\\looking-glass-host.txt");
|
|
DEBUG_INFO("Continuing to wait...");
|
|
}
|
|
|
|
SDL_WaitEventTimeout(NULL, 1000);
|
|
}
|
|
|
|
if (g_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)
|
|
{
|
|
DEBUG_BREAK();
|
|
DEBUG_ERROR("The host application is not compatible with this client");
|
|
DEBUG_ERROR("This is not a Looking Glass error, do not report this");
|
|
DEBUG_ERROR("Please install the matching host application for this client");
|
|
|
|
if (magicMatches)
|
|
{
|
|
DEBUG_ERROR("Expected KVMFR version %d, got %d", KVMFR_VERSION, udata->version);
|
|
DEBUG_ERROR("Client version: %s", BUILD_VERSION);
|
|
if (udata->version >= 2)
|
|
DEBUG_ERROR(" Host version: %s", udata->hostver);
|
|
}
|
|
else
|
|
DEBUG_ERROR("Invalid KVMFR magic");
|
|
|
|
DEBUG_BREAK();
|
|
|
|
if (magicMatches)
|
|
{
|
|
DEBUG_INFO("Waiting for you to upgrade the host application");
|
|
while (g_state.state == APP_STATE_RUNNING && udata->version != KVMFR_VERSION)
|
|
SDL_WaitEventTimeout(NULL, 1000);
|
|
|
|
if (g_state.state != APP_STATE_RUNNING)
|
|
return -1;
|
|
|
|
goto restart;
|
|
}
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
DEBUG_INFO("Host ready, reported version: %s", udata->hostver);
|
|
DEBUG_INFO("Starting session");
|
|
|
|
if (!lgCreateThread("cursorThread", cursorThread, NULL, &t_cursor))
|
|
{
|
|
DEBUG_ERROR("cursor create thread failed");
|
|
return 1;
|
|
}
|
|
|
|
if (!lgCreateThread("frameThread", frameThread, NULL, &t_frame))
|
|
{
|
|
DEBUG_ERROR("frame create thread failed");
|
|
return -1;
|
|
}
|
|
|
|
while(g_state.state == APP_STATE_RUNNING)
|
|
{
|
|
if (!lgmpClientSessionValid(g_state.lgmp))
|
|
{
|
|
g_state.state = APP_STATE_RESTART;
|
|
break;
|
|
}
|
|
SDL_WaitEventTimeout(NULL, 100);
|
|
}
|
|
|
|
if (g_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;
|
|
|
|
lgInit();
|
|
|
|
g_state.lgr->on_restart(g_state.lgrData);
|
|
|
|
DEBUG_INFO("Waiting for the host to restart...");
|
|
goto restart;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void lg_shutdown(void)
|
|
{
|
|
g_state.state = APP_STATE_SHUTDOWN;
|
|
if (t_render)
|
|
{
|
|
lgSignalEvent(e_startup);
|
|
lgSignalEvent(e_frame);
|
|
lgJoinThread(t_render, NULL);
|
|
}
|
|
|
|
lgmpClientFree(&g_state.lgmp);
|
|
|
|
if (e_frame)
|
|
{
|
|
lgFreeEvent(e_frame);
|
|
e_frame = NULL;
|
|
}
|
|
|
|
if (e_startup)
|
|
{
|
|
lgFreeEvent(e_startup);
|
|
e_startup = 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 (g_state.keyDown[i])
|
|
{
|
|
uint32_t scancode = mapScancode(i);
|
|
if (scancode == 0)
|
|
continue;
|
|
g_state.keyDown[i] = false;
|
|
spice_key_up(scancode);
|
|
}
|
|
|
|
spice_disconnect();
|
|
if (t_spice)
|
|
lgJoinThread(t_spice, NULL);
|
|
}
|
|
|
|
if (g_state.ds)
|
|
g_state.ds->shutdown();
|
|
|
|
if (g_state.cbRequestList)
|
|
{
|
|
ll_free(g_state.cbRequestList);
|
|
g_state.cbRequestList = NULL;
|
|
}
|
|
|
|
if (g_state.window)
|
|
{
|
|
g_state.ds->free();
|
|
SDL_DestroyWindow(g_state.window);
|
|
}
|
|
|
|
if (cursor)
|
|
SDL_FreeCursor(cursor);
|
|
|
|
ivshmemClose(&g_state.shm);
|
|
|
|
release_key_binds();
|
|
|
|
SDL_Quit();
|
|
}
|
|
|
|
int main(int argc, char * argv[])
|
|
{
|
|
if (getuid() == 0)
|
|
{
|
|
DEBUG_ERROR("Do not run looking glass as root!");
|
|
return -1;
|
|
}
|
|
|
|
DEBUG_INFO("Looking Glass (%s)", BUILD_VERSION);
|
|
DEBUG_INFO("Locking Method: " LG_LOCK_MODE);
|
|
|
|
if (!installCrashHandler("/proc/self/exe"))
|
|
DEBUG_WARN("Failed to install the crash handler");
|
|
|
|
config_init();
|
|
ivshmemOptionsInit();
|
|
|
|
// 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;
|
|
|
|
const int ret = lg_run();
|
|
lg_shutdown();
|
|
|
|
config_free();
|
|
return ret;
|
|
|
|
}
|