mirror of
https://github.com/gnif/LookingGlass.git
synced 2024-11-14 01:58:25 +00:00
2232 lines
54 KiB
C
2232 lines
54 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 <linux/input.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;
|
|
}
|
|
|
|
void app_handleFocusEvent(bool focused)
|
|
{
|
|
if (!app_inputEnabled())
|
|
return;
|
|
|
|
if (params.grabKeyboardOnFocus)
|
|
{
|
|
if (focused)
|
|
g_state.ds->grabKeyboard();
|
|
else
|
|
g_state.ds->ungrabKeyboard();
|
|
}
|
|
|
|
g_state.focused = focused;
|
|
g_cursor.realign = true;
|
|
g_state.ds->realignPointer();
|
|
}
|
|
|
|
void app_handleCloseEvent(void)
|
|
{
|
|
if (!params.ignoreQuit || !g_cursor.inView)
|
|
g_state.state = APP_STATE_SHUTDOWN;
|
|
}
|
|
|
|
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)
|
|
goto done;
|
|
|
|
float srcW;
|
|
float srcH;
|
|
switch(params.winRotate)
|
|
{
|
|
case LG_ROTATE_0:
|
|
case LG_ROTATE_180:
|
|
srcW = g_state.srcSize.x;
|
|
srcH = g_state.srcSize.y;
|
|
break;
|
|
|
|
case LG_ROTATE_90:
|
|
case LG_ROTATE_270:
|
|
srcW = g_state.srcSize.y;
|
|
srcH = g_state.srcSize.x;
|
|
break;
|
|
}
|
|
|
|
if (params.keepAspect)
|
|
{
|
|
const float srcAspect = srcH / srcW;
|
|
const float wndAspect = (float)g_state.windowH / (float)g_state.windowW;
|
|
bool force = true;
|
|
|
|
if (params.dontUpscale &&
|
|
srcW <= g_state.windowW &&
|
|
srcH <= g_state.windowH)
|
|
{
|
|
force = false;
|
|
g_state.dstRect.w = srcW;
|
|
g_state.dstRect.h = srcH;
|
|
g_state.dstRect.x = g_state.windowCX - srcW / 2;
|
|
g_state.dstRect.y = g_state.windowCY - srcH / 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 = (
|
|
srcH != g_state.dstRect.h ||
|
|
srcW != g_state.dstRect.w ||
|
|
g_cursor.guest.dpiScale != 100);
|
|
|
|
g_cursor.scale.x = (float)srcH / (float)g_state.dstRect.h;
|
|
g_cursor.scale.y = (float)srcW / (float)g_state.dstRect.w;
|
|
g_cursor.dpiScale = g_cursor.guest.dpiScale / 100.0f;
|
|
|
|
done:
|
|
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, params.winRotate);
|
|
atomic_compare_exchange_weak(&g_state.lgrResize, &resize, 0);
|
|
}
|
|
|
|
if (!g_state.lgr->render(g_state.lgrData, g_state.window, params.winRotate))
|
|
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,
|
|
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;
|
|
|
|
switch(frame->rotation)
|
|
{
|
|
case FRAME_ROT_0 : lgrFormat.rotate = LG_ROTATE_0 ; break;
|
|
case FRAME_ROT_90 : lgrFormat.rotate = LG_ROTATE_90 ; break;
|
|
case FRAME_ROT_180: lgrFormat.rotate = LG_ROTATE_180; break;
|
|
case FRAME_ROT_270: lgrFormat.rotate = LG_ROTATE_270; break;
|
|
}
|
|
g_state.rotate = lgrFormat.rotate;
|
|
|
|
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;
|
|
|
|
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 stride:%u pitch:%u rotation:%d",
|
|
FrameTypeStr[frame->type],
|
|
frame->width, frame->height,
|
|
frame->stride, frame->pitch,
|
|
frame->rotation);
|
|
|
|
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 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 warpPointer(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->warpPointer(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 };
|
|
|
|
/* only apply smoothing to small deltas */
|
|
if (fabs(ex - last.x) < 5.0 && fabs(ey - last.y) < 5.0)
|
|
{
|
|
ex = last.x = (last.x + ex) / 2.0;
|
|
ey = last.y = (last.y + ey) / 2.0;
|
|
}
|
|
else
|
|
{
|
|
last.x = ex;
|
|
last.y = ey;
|
|
}
|
|
}
|
|
|
|
/* 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 the state has not changed, don't do anything else
|
|
if (g_cursor.inView == enable)
|
|
return;
|
|
|
|
if (enable && !g_state.focused)
|
|
return;
|
|
|
|
g_cursor.inView = enable;
|
|
g_cursor.draw = params.alwaysShowCursor ? true : enable;
|
|
g_cursor.redraw = true;
|
|
|
|
/* 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.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();
|
|
|
|
if (!g_cursor.grab)
|
|
g_cursor.realign = true;
|
|
}
|
|
|
|
g_cursor.warpState = WARP_STATE_ON;
|
|
}
|
|
|
|
void app_handleMouseGrabbed(double ex, double ey)
|
|
{
|
|
int x, y;
|
|
|
|
/* do not pass mouse events to the guest if we do not have focus */
|
|
if (!g_state.focused)
|
|
return;
|
|
|
|
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");
|
|
}
|
|
|
|
void app_handleButtonPress(int button)
|
|
{
|
|
if (!app_inputEnabled() || !g_cursor.inView)
|
|
return;
|
|
|
|
g_cursor.buttons |= (1U << button);
|
|
|
|
if (!spice_mouse_press(button))
|
|
DEBUG_ERROR("SDL_MOUSEBUTTONDOWN: failed to send message");
|
|
}
|
|
|
|
void app_handleButtonRelease(int button)
|
|
{
|
|
if (!app_inputEnabled())
|
|
return;
|
|
|
|
g_cursor.buttons &= ~(1U << button);
|
|
|
|
if (!spice_mouse_release(button))
|
|
DEBUG_ERROR("SDL_MOUSEBUTTONUP: failed to send message");
|
|
}
|
|
|
|
void app_handleKeyPress(int sc)
|
|
{
|
|
if (sc == params.escapeKey && !g_state.escapeActive)
|
|
{
|
|
g_state.escapeActive = true;
|
|
g_state.escapeAction = -1;
|
|
return;
|
|
}
|
|
|
|
if (g_state.escapeActive)
|
|
{
|
|
g_state.escapeAction = sc;
|
|
return;
|
|
}
|
|
|
|
if (!app_inputEnabled())
|
|
return;
|
|
|
|
if (params.ignoreWindowsKeys && (sc == KEY_LEFTMETA || sc == KEY_RIGHTMETA))
|
|
return;
|
|
|
|
if (!g_state.keyDown[sc])
|
|
{
|
|
uint32_t ps2 = xfree86_to_ps2[sc];
|
|
if (!ps2)
|
|
return;
|
|
|
|
if (spice_key_down(ps2))
|
|
g_state.keyDown[sc] = true;
|
|
else
|
|
{
|
|
DEBUG_ERROR("SDL_KEYDOWN: failed to send message");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void app_handleKeyRelease(int sc)
|
|
{
|
|
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);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (sc == params.escapeKey)
|
|
g_state.escapeActive = false;
|
|
}
|
|
|
|
if (!app_inputEnabled())
|
|
return;
|
|
|
|
// avoid sending key up events when we didn't send a down
|
|
if (!g_state.keyDown[sc])
|
|
return;
|
|
|
|
if (params.ignoreWindowsKeys && (sc == KEY_LEFTMETA || sc == KEY_RIGHTMETA))
|
|
return;
|
|
|
|
uint32_t ps2 = xfree86_to_ps2[sc];
|
|
if (!ps2)
|
|
return;
|
|
|
|
if (spice_key_up(ps2))
|
|
g_state.keyDown[sc] = false;
|
|
else
|
|
{
|
|
DEBUG_ERROR("SDL_KEYUP: failed to send message");
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void rotatePoint(struct DoublePoint *point)
|
|
{
|
|
double temp;
|
|
|
|
switch((g_state.rotate + params.winRotate) % LG_ROTATE_MAX)
|
|
{
|
|
case LG_ROTATE_0:
|
|
break;
|
|
|
|
case LG_ROTATE_90:
|
|
temp = point->x;
|
|
point->x = point->y;
|
|
point->y = -temp;
|
|
break;
|
|
|
|
case LG_ROTATE_180:
|
|
point->x = -point->x;
|
|
point->y = -point->y;
|
|
break;
|
|
|
|
case LG_ROTATE_270:
|
|
temp = point->x;
|
|
point->x = -point->y;
|
|
point->y = temp;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void guestCurToLocal(struct DoublePoint *local)
|
|
{
|
|
const struct DoublePoint point =
|
|
{
|
|
.x = g_cursor.guest.x + g_cursor.guest.hx,
|
|
.y = g_cursor.guest.y + g_cursor.guest.hy
|
|
};
|
|
|
|
switch((g_state.rotate + params.winRotate) % LG_ROTATE_MAX)
|
|
{
|
|
case LG_ROTATE_0:
|
|
local->x = (point.x / g_cursor.scale.x) + g_state.dstRect.x;
|
|
local->y = (point.y / g_cursor.scale.y) + g_state.dstRect.y;;
|
|
break;
|
|
|
|
case LG_ROTATE_90:
|
|
local->x = (g_state.dstRect.x + g_state.dstRect.w) -
|
|
point.y / g_cursor.scale.y;
|
|
local->y = (point.x / g_cursor.scale.x) + g_state.dstRect.y;
|
|
break;
|
|
|
|
case LG_ROTATE_180:
|
|
local->x = (g_state.dstRect.x + g_state.dstRect.w) -
|
|
point.x / g_cursor.scale.x;
|
|
local->y = (g_state.dstRect.y + g_state.dstRect.h) -
|
|
point.y / g_cursor.scale.y;
|
|
break;
|
|
|
|
case LG_ROTATE_270:
|
|
local->x = (point.y / g_cursor.scale.y) + g_state.dstRect.x;
|
|
local->y = (g_state.dstRect.y + g_state.dstRect.h) -
|
|
point.x / g_cursor.scale.x;
|
|
break;
|
|
}
|
|
}
|
|
|
|
inline static void localCurToGuest(struct DoublePoint *guest)
|
|
{
|
|
const struct DoublePoint point =
|
|
g_cursor.pos;
|
|
|
|
switch((g_state.rotate + params.winRotate) % LG_ROTATE_MAX)
|
|
{
|
|
case LG_ROTATE_0:
|
|
guest->x = (point.x - g_state.dstRect.x) * g_cursor.scale.x;
|
|
guest->y = (point.y - g_state.dstRect.y) * g_cursor.scale.y;
|
|
break;
|
|
|
|
case LG_ROTATE_90:
|
|
guest->x = (point.y - g_state.dstRect.y) * g_cursor.scale.y;
|
|
guest->y = (g_state.dstRect.w - point.x + g_state.dstRect.x)
|
|
* g_cursor.scale.x;
|
|
break;
|
|
|
|
case LG_ROTATE_180:
|
|
guest->x = (g_state.dstRect.w - point.x + g_state.dstRect.x)
|
|
* g_cursor.scale.x;
|
|
guest->y = (g_state.dstRect.h - point.y + g_state.dstRect.y)
|
|
* g_cursor.scale.y;
|
|
break;
|
|
|
|
case LG_ROTATE_270:
|
|
guest->x = (g_state.dstRect.h - point.y + g_state.dstRect.y)
|
|
* g_cursor.scale.y;
|
|
guest->y = (point.x - g_state.dstRect.x) * g_cursor.scale.x;
|
|
break;
|
|
}
|
|
}
|
|
|
|
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 (!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;
|
|
|
|
setCursorInView(inView);
|
|
}
|
|
|
|
/* nothing to do if we are outside the viewport */
|
|
if (!g_cursor.inView)
|
|
return;
|
|
|
|
/*
|
|
* do not pass mouse events to the guest if we do not have focus, this must be
|
|
* done after the inView test has been performed so that when focus is gained
|
|
* we know if we should be drawing the cursor.
|
|
*/
|
|
if (!g_state.focused)
|
|
return;
|
|
|
|
/* if we have been instructed to realign */
|
|
if (g_cursor.realign)
|
|
{
|
|
g_cursor.realign = false;
|
|
|
|
struct DoublePoint guest;
|
|
localCurToGuest(&guest);
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* 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;
|
|
|
|
/* if any buttons are held we should not allow exit to happen */
|
|
if (g_cursor.buttons)
|
|
testExit = false;
|
|
|
|
if (testExit)
|
|
{
|
|
/* translate the move to the guests orientation */
|
|
struct DoublePoint move = {.x = ex, .y = ey};
|
|
rotatePoint(&move);
|
|
|
|
/* 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 (
|
|
local.x + move.x < g_state.dstRect.x ||
|
|
local.y + move.y < g_state.dstRect.y ||
|
|
local.x + move.x >= g_state.dstRect.x + g_state.dstRect.w ||
|
|
local.y + move.y >= g_state.dstRect.y + g_state.dstRect.h)
|
|
{
|
|
local.x += move.x;
|
|
local.y += move.y;
|
|
const int tx = (local.x <= 0.0) ? floor(local.x) : ceil(local.x);
|
|
const int ty = (local.y <= 0.0) ? floor(local.y) : ceil(local.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();
|
|
|
|
warpPointer(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;
|
|
warpPointer(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()
|
|
{
|
|
/* do not pass mouse events to the guest if we do not have focus */
|
|
if (!g_state.focused)
|
|
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;
|
|
|
|
setCursorInView(inView);
|
|
|
|
if (g_cursor.guest.dpiScale == 0)
|
|
return;
|
|
|
|
double px = g_cursor.pos.x;
|
|
double py = g_cursor.pos.y;
|
|
|
|
if (px < g_state.dstRect.x)
|
|
px = g_state.dstRect.x;
|
|
else if (px > g_state.dstRect.x + g_state.dstRect.w)
|
|
px = g_state.dstRect.x + g_state.dstRect.w;
|
|
|
|
if (py < g_state.dstRect.y)
|
|
py = g_state.dstRect.y;
|
|
else if (py > g_state.dstRect.y + g_state.dstRect.h)
|
|
py = g_state.dstRect.y + g_state.dstRect.h;
|
|
|
|
/* translate the guests position to our coordinate space */
|
|
struct DoublePoint local;
|
|
guestCurToLocal(&local);
|
|
|
|
double lx = (px - local.x) / g_cursor.dpiScale;
|
|
double ly = (py - 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;
|
|
setCursorInView(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;
|
|
|
|
g_cursor.realign = true;
|
|
}
|
|
|
|
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)
|
|
{
|
|
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)
|
|
setCursorInView(true);
|
|
}
|
|
|
|
int eventFilter(void * userdata, SDL_Event * event)
|
|
{
|
|
if (g_state.ds->eventFilter(event))
|
|
return 0;
|
|
|
|
// always include the default handler (SDL) for any unhandled events
|
|
if (g_state.ds != LG_DisplayServers[0])
|
|
if (LG_DisplayServers[0]->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);
|
|
warpPointer(round(local.x), round(local.y), false);
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// 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(uint32_t scancode, void * opaque)
|
|
{
|
|
SDL_SetWindowFullscreen(g_state.window, params.fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP);
|
|
params.fullscreen = !params.fullscreen;
|
|
}
|
|
|
|
static void toggle_video(uint32_t scancode, 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_rotate(uint32_t scancode, void * opaque)
|
|
{
|
|
if (params.winRotate == LG_ROTATE_MAX-1)
|
|
params.winRotate = 0;
|
|
else
|
|
++params.winRotate;
|
|
updatePositionInfo();
|
|
}
|
|
|
|
static void toggle_input(uint32_t scancode, void * opaque)
|
|
{
|
|
g_state.ignoreInput = !g_state.ignoreInput;
|
|
app_alert(
|
|
LG_ALERT_INFO,
|
|
g_state.ignoreInput ? "Input Disabled" : "Input Enabled"
|
|
);
|
|
}
|
|
|
|
static void quit(uint32_t scancode, void * opaque)
|
|
{
|
|
g_state.state = APP_STATE_SHUTDOWN;
|
|
}
|
|
|
|
static void mouse_sens_inc(uint32_t scancode, 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(uint32_t scancode, 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(uint32_t key, void * opaque)
|
|
{
|
|
const uint32_t ctrl = xfree86_to_ps2[KEY_LEFTCTRL];
|
|
const uint32_t alt = xfree86_to_ps2[KEY_LEFTALT ];
|
|
const uint32_t fn = xfree86_to_ps2[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(uint32_t sc, void * opaque)
|
|
{
|
|
sc = xfree86_to_ps2[sc];
|
|
spice_key_down(sc);
|
|
spice_key_up (sc);
|
|
}
|
|
|
|
static void register_key_binds(void)
|
|
{
|
|
g_state.kbFS = app_register_keybind(KEY_F , toggle_fullscreen, NULL);
|
|
g_state.kbVideo = app_register_keybind(KEY_V , toggle_video , NULL);
|
|
g_state.kbRotate = app_register_keybind(KEY_R , toggle_rotate , NULL);
|
|
g_state.kbInput = app_register_keybind(KEY_I , toggle_input , NULL);
|
|
g_state.kbQuit = app_register_keybind(KEY_Q , quit , NULL);
|
|
g_state.kbMouseSensInc = app_register_keybind(KEY_INSERT, mouse_sens_inc , NULL);
|
|
g_state.kbMouseSensDec = app_register_keybind(KEY_DELETE, mouse_sens_dec , NULL);
|
|
|
|
g_state.kbCtrlAltFn[0 ] = app_register_keybind(KEY_F1 , ctrl_alt_fn, NULL);
|
|
g_state.kbCtrlAltFn[1 ] = app_register_keybind(KEY_F2 , ctrl_alt_fn, NULL);
|
|
g_state.kbCtrlAltFn[2 ] = app_register_keybind(KEY_F3 , ctrl_alt_fn, NULL);
|
|
g_state.kbCtrlAltFn[3 ] = app_register_keybind(KEY_F4 , ctrl_alt_fn, NULL);
|
|
g_state.kbCtrlAltFn[4 ] = app_register_keybind(KEY_F5 , ctrl_alt_fn, NULL);
|
|
g_state.kbCtrlAltFn[5 ] = app_register_keybind(KEY_F6 , ctrl_alt_fn, NULL);
|
|
g_state.kbCtrlAltFn[6 ] = app_register_keybind(KEY_F7 , ctrl_alt_fn, NULL);
|
|
g_state.kbCtrlAltFn[7 ] = app_register_keybind(KEY_F8 , ctrl_alt_fn, NULL);
|
|
g_state.kbCtrlAltFn[8 ] = app_register_keybind(KEY_F9 , ctrl_alt_fn, NULL);
|
|
g_state.kbCtrlAltFn[9 ] = app_register_keybind(KEY_F10, ctrl_alt_fn, NULL);
|
|
g_state.kbCtrlAltFn[10] = app_register_keybind(KEY_F11, ctrl_alt_fn, NULL);
|
|
g_state.kbCtrlAltFn[11] = app_register_keybind(KEY_F12, ctrl_alt_fn, NULL);
|
|
|
|
g_state.kbPass[0] = app_register_keybind(KEY_LEFTMETA , key_passthrough, NULL);
|
|
g_state.kbPass[1] = app_register_keybind(KEY_RIGHTMETA, 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(warpPointer);
|
|
SET_FALLBACK(realignPointer);
|
|
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(uint32_t scancode = 0; scancode < KEY_MAX; ++scancode)
|
|
if (g_state.keyDown[scancode])
|
|
{
|
|
g_state.keyDown[scancode] = 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;
|
|
|
|
}
|