2017-10-31 08:07:16 +00:00
|
|
|
/*
|
2017-12-03 15:22:49 +00:00
|
|
|
Looking Glass - KVM FrameRelay (KVMFR) Client
|
2019-02-22 11:16:14 +00:00
|
|
|
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
2017-12-11 17:30:47 +00:00
|
|
|
https://looking-glass.hostfission.com
|
2017-10-31 08:07:16 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
*/
|
|
|
|
|
2019-03-30 01:26:06 +00:00
|
|
|
#include "main.h"
|
2019-03-30 04:52:00 +00:00
|
|
|
#include "config.h"
|
2019-03-30 01:26:06 +00:00
|
|
|
|
2017-11-25 07:20:30 +00:00
|
|
|
#include <getopt.h>
|
2017-12-17 09:09:47 +00:00
|
|
|
#include <signal.h>
|
2017-12-10 17:11:36 +00:00
|
|
|
#include <SDL2/SDL_syswm.h>
|
2017-10-19 04:15:49 +00:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
2017-12-28 04:30:03 +00:00
|
|
|
#include <sys/mman.h>
|
2019-03-30 04:52:00 +00:00
|
|
|
#include <sys/stat.h>
|
2017-12-28 04:30:03 +00:00
|
|
|
#include <fcntl.h>
|
2017-10-19 04:15:49 +00:00
|
|
|
#include <unistd.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <stdbool.h>
|
|
|
|
#include <assert.h>
|
2020-08-09 05:14:17 +00:00
|
|
|
#include <stdatomic.h>
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2020-01-13 11:21:12 +00:00
|
|
|
#if SDL_VIDEO_DRIVER_X11_XINPUT2
|
|
|
|
// because SDL2 sucks and we need to turn it off
|
|
|
|
#include <X11/extensions/XInput2.h>
|
|
|
|
#endif
|
|
|
|
|
2019-04-11 01:12:59 +00:00
|
|
|
#include "common/debug.h"
|
2019-04-11 06:41:52 +00:00
|
|
|
#include "common/crash.h"
|
2019-04-11 01:12:59 +00:00
|
|
|
#include "common/KVMFR.h"
|
2019-05-23 19:29:38 +00:00
|
|
|
#include "common/stringutils.h"
|
2020-01-02 12:59:06 +00:00
|
|
|
#include "common/thread.h"
|
2020-01-17 03:35:08 +00:00
|
|
|
#include "common/locking.h"
|
2020-01-02 13:08:43 +00:00
|
|
|
#include "common/event.h"
|
2020-01-03 04:17:14 +00:00
|
|
|
#include "common/ivshmem.h"
|
2020-01-03 04:53:44 +00:00
|
|
|
#include "common/time.h"
|
2020-10-08 15:17:20 +00:00
|
|
|
#include "common/version.h"
|
2020-01-03 04:17:14 +00:00
|
|
|
|
2017-12-10 14:31:52 +00:00
|
|
|
#include "utils.h"
|
2017-10-19 04:15:49 +00:00
|
|
|
#include "kb.h"
|
2019-02-24 18:43:18 +00:00
|
|
|
#include "ll.h"
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2020-01-03 04:53:44 +00:00
|
|
|
#define RESIZE_TIMEOUT (10 * 1000) // 10ms
|
|
|
|
|
2019-03-30 01:26:06 +00:00
|
|
|
// forwards
|
|
|
|
static int cursorThread(void * unused);
|
|
|
|
static int renderThread(void * unused);
|
|
|
|
static int frameThread (void * unused);
|
2017-11-25 06:51:34 +00:00
|
|
|
|
2020-01-02 13:08:43 +00:00
|
|
|
static LGEvent *e_startup = NULL;
|
2020-05-21 03:41:59 +00:00
|
|
|
static LGEvent *e_frame = NULL;
|
2020-01-02 13:08:43 +00:00
|
|
|
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;
|
2019-10-26 01:03:10 +00:00
|
|
|
|
|
|
|
struct AppState state;
|
2019-05-21 05:03:59 +00:00
|
|
|
|
|
|
|
// this structure is initialized in config.c
|
|
|
|
struct AppParams params = { 0 };
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2020-01-10 18:22:12 +00:00
|
|
|
static void handleMouseMoveEvent(int ex, int ey);
|
|
|
|
static void alignMouseWithGuest();
|
|
|
|
static void alignMouseWithHost();
|
2020-01-10 16:41:44 +00:00
|
|
|
|
2018-10-04 07:03:09 +00:00
|
|
|
static void updatePositionInfo()
|
2017-12-01 02:42:58 +00:00
|
|
|
{
|
2018-07-19 13:32:42 +00:00
|
|
|
if (state.haveSrcSize)
|
2017-12-01 02:42:58 +00:00
|
|
|
{
|
2018-07-19 13:32:42 +00:00
|
|
|
if (params.keepAspect)
|
2017-12-01 02:42:58 +00:00
|
|
|
{
|
2018-07-19 13:32:42 +00:00
|
|
|
const float srcAspect = (float)state.srcSize.y / (float)state.srcSize.x;
|
2018-07-28 04:49:37 +00:00
|
|
|
const float wndAspect = (float)state.windowH / (float)state.windowW;
|
2020-01-03 06:23:48 +00:00
|
|
|
bool force = true;
|
|
|
|
|
2020-10-12 12:27:33 +00:00
|
|
|
if (params.dontUpscale &&
|
|
|
|
state.srcSize.x <= state.windowW &&
|
|
|
|
state.srcSize.y <= state.windowH)
|
|
|
|
{
|
|
|
|
force = false;
|
|
|
|
state.dstRect.w = state.srcSize.x;
|
|
|
|
state.dstRect.h = state.srcSize.y;
|
|
|
|
state.dstRect.x = state.windowW / 2 - state.srcSize.x / 2;
|
|
|
|
state.dstRect.y = state.windowH / 2 - state.srcSize.y / 2;
|
|
|
|
}
|
|
|
|
else
|
2020-01-03 06:23:48 +00:00
|
|
|
if ((int)(wndAspect * 1000) == (int)(srcAspect * 1000))
|
|
|
|
{
|
|
|
|
force = false;
|
|
|
|
state.dstRect.w = state.windowW;
|
|
|
|
state.dstRect.h = state.windowH;
|
|
|
|
state.dstRect.x = 0;
|
|
|
|
state.dstRect.y = 0;
|
|
|
|
}
|
|
|
|
else
|
2018-07-19 13:32:42 +00:00
|
|
|
if (wndAspect < srcAspect)
|
|
|
|
{
|
2018-07-28 04:49:37 +00:00
|
|
|
state.dstRect.w = (float)state.windowH / srcAspect;
|
|
|
|
state.dstRect.h = state.windowH;
|
|
|
|
state.dstRect.x = (state.windowW >> 1) - (state.dstRect.w >> 1);
|
2018-07-19 13:32:42 +00:00
|
|
|
state.dstRect.y = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2018-07-28 04:49:37 +00:00
|
|
|
state.dstRect.w = state.windowW;
|
|
|
|
state.dstRect.h = (float)state.windowW * srcAspect;
|
2018-07-19 13:32:42 +00:00
|
|
|
state.dstRect.x = 0;
|
2018-07-28 04:49:37 +00:00
|
|
|
state.dstRect.y = (state.windowH >> 1) - (state.dstRect.h >> 1);
|
2018-07-19 13:32:42 +00:00
|
|
|
}
|
2020-01-03 06:23:48 +00:00
|
|
|
|
|
|
|
if (force && params.forceAspect)
|
|
|
|
{
|
2020-01-17 03:35:08 +00:00
|
|
|
state.resizeTimeout = microtime() + RESIZE_TIMEOUT;
|
2020-01-03 06:23:48 +00:00
|
|
|
state.resizeDone = false;
|
|
|
|
}
|
2017-12-01 02:42:58 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2017-12-01 02:58:29 +00:00
|
|
|
state.dstRect.x = 0;
|
2018-07-19 13:32:42 +00:00
|
|
|
state.dstRect.y = 0;
|
2018-07-28 04:49:37 +00:00
|
|
|
state.dstRect.w = state.windowW;
|
|
|
|
state.dstRect.h = state.windowH;
|
2017-12-01 02:42:58 +00:00
|
|
|
}
|
2018-07-19 13:32:42 +00:00
|
|
|
state.dstRect.valid = true;
|
2017-12-01 02:42:58 +00:00
|
|
|
|
2018-07-19 13:32:42 +00:00
|
|
|
state.scaleX = (float)state.srcSize.y / (float)state.dstRect.h;
|
|
|
|
state.scaleY = (float)state.srcSize.x / (float)state.dstRect.w;
|
|
|
|
}
|
2017-12-20 19:38:56 +00:00
|
|
|
|
2018-07-28 04:49:37 +00:00
|
|
|
state.lgrResize = true;
|
2017-12-06 15:37:46 +00:00
|
|
|
}
|
|
|
|
|
2019-03-28 10:23:24 +00:00
|
|
|
static int renderThread(void * unused)
|
2017-10-19 04:15:49 +00:00
|
|
|
{
|
2018-07-28 04:49:37 +00:00
|
|
|
if (!state.lgr->render_startup(state.lgrData, state.window))
|
2019-05-27 08:38:36 +00:00
|
|
|
{
|
2020-08-11 05:14:58 +00:00
|
|
|
state.state = APP_STATE_SHUTDOWN;
|
2018-07-28 04:49:37 +00:00
|
|
|
|
2019-12-09 16:30:04 +00:00
|
|
|
/* unblock threads waiting on the condition */
|
2020-01-02 13:08:43 +00:00
|
|
|
lgSignalEvent(e_startup);
|
2019-03-28 10:23:24 +00:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2019-12-09 16:30:04 +00:00
|
|
|
/* signal to other threads that the renderer is ready */
|
2020-01-02 13:08:43 +00:00
|
|
|
lgSignalEvent(e_startup);
|
2019-12-09 16:30:04 +00:00
|
|
|
|
2018-12-12 14:21:56 +00:00
|
|
|
struct timespec time;
|
2020-10-29 15:36:12 +00:00
|
|
|
clock_gettime(CLOCK_MONOTONIC, &time);
|
|
|
|
|
2020-08-11 05:14:58 +00:00
|
|
|
while(state.state != APP_STATE_SHUTDOWN)
|
2017-12-19 13:53:45 +00:00
|
|
|
{
|
2020-11-07 23:58:18 +00:00
|
|
|
if (params.fpsMin != 0)
|
2020-05-22 08:39:19 +00:00
|
|
|
{
|
2020-10-26 07:09:45 +00:00
|
|
|
lgWaitEventAbs(e_frame, &time);
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &time);
|
2020-08-09 05:54:45 +00:00
|
|
|
tsAdd(&time, state.frameTime);
|
2020-05-22 08:39:19 +00:00
|
|
|
}
|
|
|
|
|
2018-07-28 04:49:37 +00:00
|
|
|
if (state.lgrResize)
|
|
|
|
{
|
|
|
|
if (state.lgr)
|
|
|
|
state.lgr->on_resize(state.lgrData, state.windowW, state.windowH, state.dstRect);
|
|
|
|
state.lgrResize = false;
|
|
|
|
}
|
|
|
|
|
2018-05-28 05:30:04 +00:00
|
|
|
if (!state.lgr->render(state.lgrData, state.window))
|
|
|
|
break;
|
2018-05-23 23:01:53 +00:00
|
|
|
|
2018-12-12 14:28:00 +00:00
|
|
|
if (params.showFPS)
|
2018-11-19 18:26:51 +00:00
|
|
|
{
|
2018-12-12 14:28:00 +00:00
|
|
|
const uint64_t t = nanotime();
|
|
|
|
state.renderTime += t - state.lastFrameTime;
|
|
|
|
state.lastFrameTime = t;
|
|
|
|
++state.renderCount;
|
|
|
|
|
|
|
|
if (state.renderTime > 1e9)
|
|
|
|
{
|
2020-10-26 07:09:45 +00:00
|
|
|
const float avgUPS = 1000.0f / (((float)state.renderTime /
|
|
|
|
atomic_exchange_explicit(&state.frameCount, 0, memory_order_acquire)) /
|
|
|
|
1e6f);
|
|
|
|
|
|
|
|
const float avgFPS = 1000.0f / (((float)state.renderTime /
|
|
|
|
state.renderCount) /
|
|
|
|
1e6f);
|
|
|
|
|
2018-12-12 14:28:00 +00:00
|
|
|
state.lgr->update_fps(state.lgrData, avgUPS, avgFPS);
|
2018-12-12 14:21:56 +00:00
|
|
|
|
2018-12-12 14:28:00 +00:00
|
|
|
state.renderTime = 0;
|
|
|
|
state.renderCount = 0;
|
|
|
|
}
|
2018-11-19 18:26:51 +00:00
|
|
|
}
|
|
|
|
|
2020-01-17 03:35:08 +00:00
|
|
|
if (!state.resizeDone && state.resizeTimeout < microtime())
|
2020-01-03 04:53:44 +00:00
|
|
|
{
|
|
|
|
SDL_SetWindowSize(
|
|
|
|
state.window,
|
|
|
|
state.dstRect.w,
|
|
|
|
state.dstRect.h
|
|
|
|
);
|
|
|
|
state.resizeDone = true;
|
|
|
|
}
|
2017-12-19 13:53:45 +00:00
|
|
|
}
|
2018-01-24 12:46:11 +00:00
|
|
|
|
2020-08-11 05:14:58 +00:00
|
|
|
state.state = APP_STATE_SHUTDOWN;
|
2019-08-30 01:50:43 +00:00
|
|
|
|
|
|
|
if (t_cursor)
|
2020-01-02 12:59:06 +00:00
|
|
|
lgJoinThread(t_cursor, NULL);
|
2019-08-30 01:50:43 +00:00
|
|
|
|
2020-01-02 12:59:06 +00:00
|
|
|
if (t_frame)
|
|
|
|
lgJoinThread(t_frame, NULL);
|
2019-08-30 01:36:28 +00:00
|
|
|
|
|
|
|
state.lgr->deinitialize(state.lgrData);
|
|
|
|
state.lgr = NULL;
|
2018-01-24 12:46:11 +00:00
|
|
|
return 0;
|
2017-12-19 13:53:45 +00:00
|
|
|
}
|
2017-12-15 08:13:36 +00:00
|
|
|
|
2019-03-28 10:23:24 +00:00
|
|
|
static int cursorThread(void * unused)
|
2017-12-19 13:53:45 +00:00
|
|
|
{
|
2020-01-09 09:32:42 +00:00
|
|
|
LGMP_STATUS status;
|
2020-01-10 00:00:46 +00:00
|
|
|
PLGMPClientQueue queue;
|
2017-12-15 08:13:36 +00:00
|
|
|
LG_RendererCursor cursorType = LG_CURSOR_COLOR;
|
2017-12-15 01:02:08 +00:00
|
|
|
|
2020-01-02 13:08:43 +00:00
|
|
|
lgWaitEvent(e_startup, TIMEOUT_INFINITE);
|
2020-01-09 09:32:42 +00:00
|
|
|
|
|
|
|
// subscribe to the pointer queue
|
2020-08-11 05:14:58 +00:00
|
|
|
while(state.state == APP_STATE_RUNNING)
|
2017-12-06 15:37:46 +00:00
|
|
|
{
|
2020-01-09 09:32:42 +00:00
|
|
|
status = lgmpClientSubscribe(state.lgmp, LGMP_Q_POINTER, &queue);
|
|
|
|
if (status == LGMP_OK)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (status == LGMP_ERR_NO_SUCH_QUEUE)
|
2017-12-20 14:56:59 +00:00
|
|
|
{
|
2020-01-09 09:32:42 +00:00
|
|
|
usleep(1000);
|
2017-12-20 14:56:59 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-01-09 09:32:42 +00:00
|
|
|
DEBUG_ERROR("lgmpClientSubscribe Failed: %s", lgmpStatusString(status));
|
2020-08-11 05:14:58 +00:00
|
|
|
state.state = APP_STATE_SHUTDOWN;
|
2020-01-09 09:32:42 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-08-11 05:14:58 +00:00
|
|
|
while(state.state == APP_STATE_RUNNING)
|
2020-01-09 09:32:42 +00:00
|
|
|
{
|
|
|
|
LGMPMessage msg;
|
|
|
|
if ((status = lgmpClientProcess(queue, &msg)) != LGMP_OK)
|
2019-01-11 12:59:46 +00:00
|
|
|
{
|
2020-01-09 09:32:42 +00:00
|
|
|
if (status == LGMP_ERR_QUEUE_EMPTY)
|
|
|
|
{
|
2020-01-10 19:03:16 +00:00
|
|
|
if (state.updateCursor)
|
|
|
|
{
|
|
|
|
state.updateCursor = false;
|
|
|
|
state.lgr->on_mouse_event
|
|
|
|
(
|
|
|
|
state.lgrData,
|
2020-08-21 05:40:22 +00:00
|
|
|
state.cursorVisible && state.drawCursor,
|
2020-01-10 19:03:16 +00:00
|
|
|
state.cursor.x,
|
|
|
|
state.cursor.y
|
|
|
|
);
|
2020-08-20 06:05:55 +00:00
|
|
|
|
|
|
|
lgSignalEvent(e_frame);
|
2020-01-10 19:03:16 +00:00
|
|
|
}
|
|
|
|
|
2020-01-09 09:32:42 +00:00
|
|
|
usleep(params.cursorPollInterval);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-08-11 04:30:44 +00:00
|
|
|
if (status == LGMP_ERR_INVALID_SESSION)
|
2020-08-11 05:14:58 +00:00
|
|
|
state.state = APP_STATE_RESTART;
|
2020-08-11 04:45:43 +00:00
|
|
|
else
|
2020-08-11 05:14:58 +00:00
|
|
|
{
|
2020-08-11 04:45:43 +00:00
|
|
|
DEBUG_ERROR("lgmpClientProcess Failed: %s", lgmpStatusString(status));
|
2020-08-11 05:14:58 +00:00
|
|
|
state.state = APP_STATE_SHUTDOWN;
|
|
|
|
}
|
2020-01-09 09:32:42 +00:00
|
|
|
break;
|
2019-01-11 12:59:46 +00:00
|
|
|
}
|
|
|
|
|
2020-01-09 09:32:42 +00:00
|
|
|
KVMFRCursor * cursor = (KVMFRCursor *)msg.mem;
|
2020-01-10 18:22:12 +00:00
|
|
|
|
2020-01-26 15:07:32 +00:00
|
|
|
state.cursorVisible =
|
|
|
|
msg.udata & CURSOR_FLAG_VISIBLE;
|
|
|
|
|
2020-01-26 06:30:16 +00:00
|
|
|
if (msg.udata & CURSOR_FLAG_SHAPE)
|
2019-01-11 12:59:46 +00:00
|
|
|
{
|
2020-01-09 10:18:35 +00:00
|
|
|
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;
|
|
|
|
}
|
2019-01-11 12:59:46 +00:00
|
|
|
|
2020-08-20 03:46:18 +00:00
|
|
|
state.cursor.hx = cursor->hx;
|
|
|
|
state.cursor.hy = cursor->hy;
|
|
|
|
|
2020-01-09 10:18:35 +00:00
|
|
|
const uint8_t * data = (const uint8_t *)(cursor + 1);
|
|
|
|
if (!state.lgr->on_mouse_shape(
|
|
|
|
state.lgrData,
|
|
|
|
cursorType,
|
|
|
|
cursor->width,
|
|
|
|
cursor->height,
|
|
|
|
cursor->pitch,
|
|
|
|
data)
|
|
|
|
)
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("Failed to update mouse shape");
|
2020-01-09 09:32:42 +00:00
|
|
|
lgmpClientMessageDone(queue);
|
|
|
|
continue;
|
2020-01-09 10:18:35 +00:00
|
|
|
}
|
2020-01-09 09:32:42 +00:00
|
|
|
}
|
2017-11-25 09:21:57 +00:00
|
|
|
|
2020-08-20 04:52:24 +00:00
|
|
|
if (msg.udata & CURSOR_FLAG_POSITION)
|
|
|
|
{
|
|
|
|
state.cursor.x = cursor->x;
|
|
|
|
state.cursor.y = cursor->y;
|
|
|
|
state.haveCursorPos = true;
|
|
|
|
|
|
|
|
if (state.haveSrcSize && state.haveCurLocal && !state.serverMode)
|
|
|
|
alignMouseWithGuest();
|
|
|
|
}
|
|
|
|
|
2020-01-09 09:32:42 +00:00
|
|
|
lgmpClientMessageDone(queue);
|
2020-01-10 19:03:16 +00:00
|
|
|
state.updateCursor = false;
|
2020-01-26 06:30:16 +00:00
|
|
|
|
2020-01-26 15:11:21 +00:00
|
|
|
state.lgr->on_mouse_event
|
|
|
|
(
|
|
|
|
state.lgrData,
|
2020-08-21 05:40:22 +00:00
|
|
|
state.cursorVisible && state.drawCursor,
|
2020-01-26 15:11:21 +00:00
|
|
|
state.cursor.x,
|
|
|
|
state.cursor.y
|
|
|
|
);
|
2020-08-20 05:50:33 +00:00
|
|
|
|
|
|
|
if (params.mouseRedraw)
|
|
|
|
lgSignalEvent(e_frame);
|
2017-12-19 13:53:45 +00:00
|
|
|
}
|
2017-12-15 07:47:44 +00:00
|
|
|
|
2020-01-09 09:32:42 +00:00
|
|
|
lgmpClientUnsubscribe(&queue);
|
2017-12-19 13:53:45 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2017-12-15 08:13:36 +00:00
|
|
|
|
2019-03-28 10:23:24 +00:00
|
|
|
static int frameThread(void * unused)
|
2017-12-19 13:53:45 +00:00
|
|
|
{
|
2020-10-29 11:57:04 +00:00
|
|
|
struct DMAFrameInfo
|
|
|
|
{
|
|
|
|
KVMFRFrame * frame;
|
|
|
|
size_t dataSize;
|
|
|
|
int fd;
|
|
|
|
};
|
|
|
|
|
2020-01-10 00:00:46 +00:00
|
|
|
LGMP_STATUS status;
|
|
|
|
PLGMPClientQueue queue;
|
2017-12-15 08:13:36 +00:00
|
|
|
|
2020-10-12 07:52:37 +00:00
|
|
|
uint32_t formatVer = 0;
|
|
|
|
bool formatValid = false;
|
2020-10-29 11:57:04 +00:00
|
|
|
size_t dataSize;
|
2020-10-12 07:52:37 +00:00
|
|
|
LG_RendererFormat lgrFormat;
|
|
|
|
|
2020-10-30 07:48:41 +00:00
|
|
|
struct DMAFrameInfo dmaInfo[LGMP_Q_FRAME_LEN] = {0};
|
2020-10-29 15:32:25 +00:00
|
|
|
const bool useDMA =
|
|
|
|
params.allowDMA &&
|
|
|
|
ivshmemHasDMA(&state.shm) &&
|
|
|
|
state.lgr->supports &&
|
2020-10-29 11:57:04 +00:00
|
|
|
state.lgr->supports(state.lgrData, LG_SUPPORTS_DMABUF);
|
|
|
|
|
|
|
|
if (useDMA)
|
|
|
|
DEBUG_INFO("Using DMA buffer support");
|
|
|
|
|
2018-10-04 07:03:09 +00:00
|
|
|
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
|
2020-01-02 13:08:43 +00:00
|
|
|
lgWaitEvent(e_startup, TIMEOUT_INFINITE);
|
2020-08-11 05:14:58 +00:00
|
|
|
if (state.state != APP_STATE_RUNNING)
|
2020-01-27 01:25:47 +00:00
|
|
|
return 0;
|
2020-01-09 09:32:42 +00:00
|
|
|
|
|
|
|
// subscribe to the frame queue
|
2020-08-11 05:14:58 +00:00
|
|
|
while(state.state == APP_STATE_RUNNING)
|
2017-12-19 13:53:45 +00:00
|
|
|
{
|
2020-01-09 09:32:42 +00:00
|
|
|
status = lgmpClientSubscribe(state.lgmp, LGMP_Q_FRAME, &queue);
|
|
|
|
if (status == LGMP_OK)
|
|
|
|
break;
|
2018-01-03 22:25:17 +00:00
|
|
|
|
2020-01-09 09:32:42 +00:00
|
|
|
if (status == LGMP_ERR_NO_SUCH_QUEUE)
|
|
|
|
{
|
|
|
|
usleep(1000);
|
2017-12-19 13:53:45 +00:00
|
|
|
continue;
|
|
|
|
}
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2020-01-09 09:32:42 +00:00
|
|
|
DEBUG_ERROR("lgmpClientSubscribe Failed: %s", lgmpStatusString(status));
|
2020-08-11 05:14:58 +00:00
|
|
|
state.state = APP_STATE_SHUTDOWN;
|
2020-01-09 09:32:42 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-10-14 08:32:21 +00:00
|
|
|
while(state.state == APP_STATE_RUNNING && !state.stopVideo)
|
2020-01-09 09:32:42 +00:00
|
|
|
{
|
|
|
|
LGMPMessage msg;
|
|
|
|
if ((status = lgmpClientProcess(queue, &msg)) != LGMP_OK)
|
|
|
|
{
|
|
|
|
if (status == LGMP_ERR_QUEUE_EMPTY)
|
|
|
|
{
|
|
|
|
usleep(params.framePollInterval);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-08-11 04:30:44 +00:00
|
|
|
if (status == LGMP_ERR_INVALID_SESSION)
|
2020-08-11 05:14:58 +00:00
|
|
|
state.state = APP_STATE_RESTART;
|
2020-08-11 04:45:43 +00:00
|
|
|
else
|
2020-08-11 05:14:58 +00:00
|
|
|
{
|
2020-08-11 04:45:43 +00:00
|
|
|
DEBUG_ERROR("lgmpClientProcess Failed: %s", lgmpStatusString(status));
|
2020-08-11 05:14:58 +00:00
|
|
|
state.state = APP_STATE_SHUTDOWN;
|
|
|
|
}
|
2020-01-09 09:32:42 +00:00
|
|
|
break;
|
2017-11-25 09:21:57 +00:00
|
|
|
}
|
2017-12-12 15:22:47 +00:00
|
|
|
|
2020-10-29 11:57:04 +00:00
|
|
|
KVMFRFrame * frame = (KVMFRFrame *)msg.mem;
|
|
|
|
struct DMAFrameInfo *dma = NULL;
|
2020-01-09 09:32:42 +00:00
|
|
|
|
2020-10-12 07:52:37 +00:00
|
|
|
if (!formatValid || frame->formatVer != formatVer)
|
2017-12-12 15:22:47 +00:00
|
|
|
{
|
2020-10-12 07:52:37 +00:00
|
|
|
// 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;
|
|
|
|
|
2020-10-29 11:57:04 +00:00
|
|
|
bool error = false;
|
2020-10-12 07:52:37 +00:00
|
|
|
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;
|
2020-10-11 08:22:31 +00:00
|
|
|
|
2020-10-12 07:52:37 +00:00
|
|
|
default:
|
|
|
|
DEBUG_ERROR("Unsupported frameType");
|
|
|
|
error = true;
|
|
|
|
break;
|
|
|
|
}
|
2018-07-27 22:41:15 +00:00
|
|
|
|
2020-10-12 07:52:37 +00:00
|
|
|
if (error)
|
|
|
|
{
|
|
|
|
lgmpClientMessageDone(queue);
|
|
|
|
state.state = APP_STATE_SHUTDOWN;
|
2017-12-19 13:53:45 +00:00
|
|
|
break;
|
2020-10-12 07:52:37 +00:00
|
|
|
}
|
2017-12-12 16:08:13 +00:00
|
|
|
|
2020-10-12 07:52:37 +00:00
|
|
|
formatValid = true;
|
|
|
|
formatVer = frame->formatVer;
|
2020-10-12 08:43:29 +00:00
|
|
|
|
|
|
|
DEBUG_INFO("Format: %s %ux%u %u %u",
|
|
|
|
FrameTypeStr[frame->type],
|
|
|
|
frame->width, frame->height,
|
|
|
|
frame->stride, frame->pitch);
|
|
|
|
|
2020-10-29 15:32:25 +00:00
|
|
|
if (!state.lgr->on_frame_format(state.lgrData, lgrFormat, useDMA))
|
2020-10-12 08:43:29 +00:00
|
|
|
{
|
|
|
|
DEBUG_ERROR("renderer failed to configure format");
|
|
|
|
state.state = APP_STATE_SHUTDOWN;
|
|
|
|
break;
|
|
|
|
}
|
2017-12-19 13:53:45 +00:00
|
|
|
}
|
2017-12-15 08:13:36 +00:00
|
|
|
|
2020-10-29 11:57:04 +00:00
|
|
|
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)state.shm.mem;
|
|
|
|
const uintptr_t offset = (uintptr_t)frame->offset + FrameBufferStructSize;
|
|
|
|
|
|
|
|
dma->dataSize = dataSize;
|
|
|
|
dma->fd = ivshmemGetDMABuf(&state.shm, pos + offset, dataSize);
|
|
|
|
if (dma->fd < 0)
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("Failed to get the DMA buffer for the frame");
|
|
|
|
state.state = APP_STATE_SHUTDOWN;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-12 07:52:37 +00:00
|
|
|
if (lgrFormat.width != state.srcSize.x || lgrFormat.height != state.srcSize.y)
|
2017-12-19 13:53:45 +00:00
|
|
|
{
|
2020-10-12 07:52:37 +00:00
|
|
|
state.srcSize.x = lgrFormat.width;
|
|
|
|
state.srcSize.y = lgrFormat.height;
|
2018-07-19 13:32:42 +00:00
|
|
|
state.haveSrcSize = true;
|
2017-12-19 13:53:45 +00:00
|
|
|
if (params.autoResize)
|
2020-10-12 07:52:37 +00:00
|
|
|
SDL_SetWindowSize(state.window, lgrFormat.width, lgrFormat.height);
|
2020-01-10 16:41:44 +00:00
|
|
|
|
2017-12-19 13:53:45 +00:00
|
|
|
updatePositionInfo();
|
|
|
|
}
|
2019-12-14 05:20:17 +00:00
|
|
|
|
2020-01-13 08:30:49 +00:00
|
|
|
FrameBuffer * fb = (FrameBuffer *)(((uint8_t*)frame) + frame->offset);
|
2020-10-29 11:57:04 +00:00
|
|
|
if (!state.lgr->on_frame(state.lgrData, fb, useDMA ? dma->fd : -1))
|
2017-12-19 13:53:45 +00:00
|
|
|
{
|
2020-10-12 08:43:29 +00:00
|
|
|
lgmpClientMessageDone(queue);
|
|
|
|
DEBUG_ERROR("renderer on frame returned failure");
|
2020-08-11 05:14:58 +00:00
|
|
|
state.state = APP_STATE_SHUTDOWN;
|
2017-12-19 13:53:45 +00:00
|
|
|
break;
|
2017-12-12 15:22:47 +00:00
|
|
|
}
|
2020-05-22 08:00:18 +00:00
|
|
|
|
2020-08-09 05:14:17 +00:00
|
|
|
atomic_fetch_add_explicit(&state.frameCount, 1, memory_order_relaxed);
|
2020-10-26 07:09:45 +00:00
|
|
|
lgSignalEvent(e_frame);
|
2020-08-09 05:14:17 +00:00
|
|
|
lgmpClientMessageDone(queue);
|
2017-10-19 04:15:49 +00:00
|
|
|
}
|
|
|
|
|
2020-01-09 09:32:42 +00:00
|
|
|
lgmpClientUnsubscribe(&queue);
|
2020-10-14 08:40:20 +00:00
|
|
|
state.lgr->on_restart(state.lgrData);
|
|
|
|
|
2020-10-29 11:57:04 +00:00
|
|
|
if (useDMA)
|
|
|
|
{
|
|
|
|
for(int i = 0; i < sizeof(dmaInfo) / sizeof(struct DMAFrameInfo); ++i)
|
|
|
|
if (dmaInfo[i].fd >= 0)
|
|
|
|
close(dmaInfo[i].fd);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-10-19 04:15:49 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int spiceThread(void * arg)
|
|
|
|
{
|
2020-08-11 05:14:58 +00:00
|
|
|
while(state.state != APP_STATE_SHUTDOWN)
|
2020-01-18 19:51:21 +00:00
|
|
|
if (!spice_process(1000))
|
2017-10-19 04:15:49 +00:00
|
|
|
{
|
2020-08-11 05:14:58 +00:00
|
|
|
if (state.state != APP_STATE_SHUTDOWN)
|
2017-10-19 07:50:42 +00:00
|
|
|
{
|
2020-08-11 05:14:58 +00:00
|
|
|
state.state = APP_STATE_SHUTDOWN;
|
2017-10-19 07:50:42 +00:00
|
|
|
DEBUG_ERROR("failed to process spice messages");
|
|
|
|
}
|
2017-10-19 04:15:49 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-08-11 05:14:58 +00:00
|
|
|
state.state = APP_STATE_SHUTDOWN;
|
2017-10-19 04:15:49 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline const uint32_t mapScancode(SDL_Scancode scancode)
|
|
|
|
{
|
|
|
|
uint32_t ps2;
|
|
|
|
if (scancode > (sizeof(usb_to_ps2) / sizeof(uint32_t)) || (ps2 = usb_to_ps2[scancode]) == 0)
|
|
|
|
{
|
|
|
|
DEBUG_WARN("Unable to map USB scan code: %x\n", scancode);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return ps2;
|
|
|
|
}
|
|
|
|
|
2019-02-24 00:43:32 +00:00
|
|
|
static LG_ClipboardData spice_type_to_clipboard_type(const SpiceDataType type)
|
|
|
|
{
|
|
|
|
switch(type)
|
|
|
|
{
|
|
|
|
case SPICE_DATA_TEXT: return LG_CLIPBOARD_DATA_TEXT; break;
|
|
|
|
case SPICE_DATA_PNG : return LG_CLIPBOARD_DATA_PNG ; break;
|
|
|
|
case SPICE_DATA_BMP : return LG_CLIPBOARD_DATA_BMP ; break;
|
|
|
|
case SPICE_DATA_TIFF: return LG_CLIPBOARD_DATA_TIFF; break;
|
|
|
|
case SPICE_DATA_JPEG: return LG_CLIPBOARD_DATA_JPEG; break;
|
|
|
|
default:
|
|
|
|
DEBUG_ERROR("invalid spice data type");
|
|
|
|
return LG_CLIPBOARD_DATA_NONE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static SpiceDataType clipboard_type_to_spice_type(const LG_ClipboardData type)
|
|
|
|
{
|
|
|
|
switch(type)
|
|
|
|
{
|
|
|
|
case LG_CLIPBOARD_DATA_TEXT: return SPICE_DATA_TEXT; break;
|
|
|
|
case LG_CLIPBOARD_DATA_PNG : return SPICE_DATA_PNG ; break;
|
|
|
|
case LG_CLIPBOARD_DATA_BMP : return SPICE_DATA_BMP ; break;
|
|
|
|
case LG_CLIPBOARD_DATA_TIFF: return SPICE_DATA_TIFF; break;
|
|
|
|
case LG_CLIPBOARD_DATA_JPEG: return SPICE_DATA_JPEG; break;
|
|
|
|
default:
|
|
|
|
DEBUG_ERROR("invalid clipboard data type");
|
|
|
|
return SPICE_DATA_NONE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void clipboardRelease()
|
|
|
|
{
|
2019-02-25 16:06:53 +00:00
|
|
|
if (!params.clipboardToVM)
|
|
|
|
return;
|
|
|
|
|
2019-02-24 00:43:32 +00:00
|
|
|
spice_clipboard_release();
|
|
|
|
}
|
|
|
|
|
|
|
|
void clipboardNotify(const LG_ClipboardData type)
|
|
|
|
{
|
2019-02-25 16:06:53 +00:00
|
|
|
if (!params.clipboardToVM)
|
|
|
|
return;
|
|
|
|
|
2019-02-24 00:43:32 +00:00
|
|
|
if (type == LG_CLIPBOARD_DATA_NONE)
|
|
|
|
{
|
|
|
|
spice_clipboard_release();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
spice_clipboard_grab(clipboard_type_to_spice_type(type));
|
|
|
|
}
|
|
|
|
|
|
|
|
void clipboardData(const LG_ClipboardData type, uint8_t * data, size_t size)
|
|
|
|
{
|
2019-02-25 16:06:53 +00:00
|
|
|
if (!params.clipboardToVM)
|
|
|
|
return;
|
|
|
|
|
2019-02-24 04:35:31 +00:00
|
|
|
uint8_t * buffer = data;
|
|
|
|
|
|
|
|
// unix2dos
|
|
|
|
if (type == LG_CLIPBOARD_DATA_TEXT)
|
|
|
|
{
|
|
|
|
// TODO: make this more memory efficent
|
2019-02-24 18:43:18 +00:00
|
|
|
size_t newSize = 0;
|
2019-02-24 04:35:31 +00:00
|
|
|
buffer = malloc(size * 2);
|
|
|
|
uint8_t * p = buffer;
|
|
|
|
for(uint32_t i = 0; i < size; ++i)
|
|
|
|
{
|
|
|
|
uint8_t c = data[i];
|
|
|
|
if (c == '\n')
|
2019-02-24 18:43:18 +00:00
|
|
|
{
|
2019-02-24 04:35:31 +00:00
|
|
|
*p++ = '\r';
|
2019-02-24 18:43:18 +00:00
|
|
|
++newSize;
|
|
|
|
}
|
2019-02-24 04:35:31 +00:00
|
|
|
*p++ = c;
|
2019-02-24 18:43:18 +00:00
|
|
|
++newSize;
|
2019-02-24 04:35:31 +00:00
|
|
|
}
|
2019-02-24 18:43:18 +00:00
|
|
|
size = newSize;
|
2019-02-24 04:35:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
spice_clipboard_data(clipboard_type_to_spice_type(type), buffer, (uint32_t)size);
|
|
|
|
if (buffer != data)
|
|
|
|
free(buffer);
|
2019-02-24 00:43:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void clipboardRequest(const LG_ClipboardReplyFn replyFn, void * opaque)
|
2019-02-22 17:24:30 +00:00
|
|
|
{
|
2019-02-25 16:06:53 +00:00
|
|
|
if (!params.clipboardToLocal)
|
|
|
|
return;
|
|
|
|
|
2019-02-24 18:43:18 +00:00
|
|
|
struct CBRequest * cbr = (struct CBRequest *)malloc(sizeof(struct CBRequest()));
|
|
|
|
|
|
|
|
cbr->type = state.cbType;
|
|
|
|
cbr->replyFn = replyFn;
|
|
|
|
cbr->opaque = opaque;
|
|
|
|
ll_push(state.cbRequestList, cbr);
|
2019-02-22 17:24:30 +00:00
|
|
|
|
|
|
|
spice_clipboard_request(state.cbType);
|
|
|
|
}
|
2019-02-22 07:59:45 +00:00
|
|
|
|
2019-02-22 08:51:14 +00:00
|
|
|
void spiceClipboardNotice(const SpiceDataType type)
|
2019-02-22 07:59:45 +00:00
|
|
|
{
|
2019-02-25 16:06:53 +00:00
|
|
|
if (!params.clipboardToLocal)
|
|
|
|
return;
|
|
|
|
|
2019-02-22 17:24:30 +00:00
|
|
|
if (!state.lgc || !state.lgc->notice)
|
|
|
|
return;
|
|
|
|
|
|
|
|
state.cbType = type;
|
2019-02-24 00:43:32 +00:00
|
|
|
state.lgc->notice(clipboardRequest, spice_type_to_clipboard_type(type));
|
2019-02-22 07:59:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void spiceClipboardData(const SpiceDataType type, uint8_t * buffer, uint32_t size)
|
|
|
|
{
|
2019-02-25 16:06:53 +00:00
|
|
|
if (!params.clipboardToLocal)
|
|
|
|
return;
|
|
|
|
|
2019-02-22 17:24:30 +00:00
|
|
|
if (type == SPICE_DATA_TEXT)
|
2019-02-22 07:59:45 +00:00
|
|
|
{
|
2019-02-22 17:24:30 +00:00
|
|
|
// dos2unix
|
2019-02-24 18:43:18 +00:00
|
|
|
uint8_t * p = buffer;
|
|
|
|
uint32_t newSize = size;
|
2019-02-22 17:24:30 +00:00
|
|
|
for(uint32_t i = 0; i < size; ++i)
|
|
|
|
{
|
|
|
|
uint8_t c = buffer[i];
|
2019-02-24 18:43:18 +00:00
|
|
|
if (c == '\r')
|
|
|
|
{
|
|
|
|
--newSize;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
*p++ = c;
|
2019-02-22 17:24:30 +00:00
|
|
|
}
|
2019-02-24 18:43:18 +00:00
|
|
|
size = newSize;
|
2019-02-22 07:59:45 +00:00
|
|
|
}
|
2019-02-22 17:24:30 +00:00
|
|
|
|
2019-02-24 18:43:18 +00:00
|
|
|
struct CBRequest * cbr;
|
|
|
|
if (ll_shift(state.cbRequestList, (void **)&cbr))
|
|
|
|
{
|
2020-10-29 02:31:14 +00:00
|
|
|
cbr->replyFn(cbr->opaque, spice_type_to_clipboard_type(type), buffer, size);
|
2019-02-24 18:43:18 +00:00
|
|
|
free(cbr);
|
|
|
|
}
|
2019-02-22 07:59:45 +00:00
|
|
|
}
|
|
|
|
|
2019-02-24 00:43:32 +00:00
|
|
|
void spiceClipboardRelease()
|
|
|
|
{
|
2019-02-25 16:06:53 +00:00
|
|
|
if (!params.clipboardToLocal)
|
|
|
|
return;
|
|
|
|
|
2019-02-24 00:43:32 +00:00
|
|
|
if (state.lgc && state.lgc->release)
|
|
|
|
state.lgc->release();
|
|
|
|
}
|
|
|
|
|
|
|
|
void spiceClipboardRequest(const SpiceDataType type)
|
|
|
|
{
|
2019-02-25 16:06:53 +00:00
|
|
|
if (!params.clipboardToVM)
|
|
|
|
return;
|
|
|
|
|
2019-02-24 00:43:32 +00:00
|
|
|
if (state.lgc && state.lgc->request)
|
|
|
|
state.lgc->request(spice_type_to_clipboard_type(type));
|
|
|
|
}
|
|
|
|
|
2020-08-20 04:52:24 +00:00
|
|
|
static void warpMouse(int x, int y)
|
|
|
|
{
|
|
|
|
if (state.warpState != WARP_STATE_ON)
|
|
|
|
return;
|
|
|
|
|
|
|
|
state.warpFromX = state.curLastX;
|
|
|
|
state.warpFromY = state.curLastY;
|
|
|
|
state.warpToX = x;
|
|
|
|
state.warpToY = y;
|
|
|
|
state.warpState = WARP_STATE_ACTIVE;
|
|
|
|
|
|
|
|
SDL_WarpMouseInWindow(state.window, x, y);
|
|
|
|
}
|
|
|
|
|
2020-01-10 16:41:44 +00:00
|
|
|
static void handleMouseMoveEvent(int ex, int ey)
|
2018-01-24 12:46:11 +00:00
|
|
|
{
|
2020-01-10 17:53:46 +00:00
|
|
|
|
2020-01-10 19:03:16 +00:00
|
|
|
state.curLocalX = ex;
|
|
|
|
state.curLocalY = ey;
|
|
|
|
state.haveCurLocal = true;
|
|
|
|
|
2020-01-10 16:41:44 +00:00
|
|
|
if (state.ignoreInput || !params.useSpiceInput)
|
|
|
|
return;
|
2017-10-26 19:38:48 +00:00
|
|
|
|
2020-08-20 04:52:24 +00:00
|
|
|
if (state.warpState == WARP_STATE_ACTIVE)
|
2020-01-10 16:41:44 +00:00
|
|
|
{
|
2020-08-20 04:52:24 +00:00
|
|
|
if (ex == state.warpToX && ey == state.warpToY)
|
2020-01-10 17:53:46 +00:00
|
|
|
{
|
2020-08-20 04:52:24 +00:00
|
|
|
state.curLastX += state.warpToX - state.warpFromX;
|
|
|
|
state.curLastY += state.warpToY - state.warpFromY;
|
2020-11-09 11:24:40 +00:00
|
|
|
state.warpState = state.serverMode ? WARP_STATE_ON : WARP_STATE_ARMED;
|
2020-01-10 17:53:46 +00:00
|
|
|
}
|
2020-08-20 04:52:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (state.serverMode)
|
|
|
|
{
|
|
|
|
if (
|
|
|
|
ex < 100 || ex > state.windowW - 100 ||
|
|
|
|
ey < 100 || ey > state.windowH - 100)
|
2020-01-10 17:53:46 +00:00
|
|
|
{
|
2020-08-20 04:52:24 +00:00
|
|
|
warpMouse(state.windowW / 2, state.windowH / 2);
|
2020-01-10 17:53:46 +00:00
|
|
|
}
|
2020-01-10 16:41:44 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (ex < state.dstRect.x ||
|
|
|
|
ex > state.dstRect.x + state.dstRect.w ||
|
|
|
|
ey < state.dstRect.y ||
|
|
|
|
ey > state.dstRect.y + state.dstRect.h)
|
2020-01-11 01:56:46 +00:00
|
|
|
{
|
|
|
|
state.cursorInView = false;
|
|
|
|
state.updateCursor = true;
|
2020-08-20 04:52:24 +00:00
|
|
|
state.warpState = WARP_STATE_OFF;
|
2020-08-21 05:40:22 +00:00
|
|
|
|
2020-11-15 13:41:22 +00:00
|
|
|
if (params.useSpiceInput && !params.alwaysShowCursor)
|
2020-08-21 05:40:22 +00:00
|
|
|
state.drawCursor = false;
|
2020-01-10 16:41:44 +00:00
|
|
|
return;
|
2020-01-11 01:56:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!state.cursorInView)
|
|
|
|
{
|
|
|
|
state.cursorInView = true;
|
|
|
|
state.updateCursor = true;
|
2020-08-21 05:40:22 +00:00
|
|
|
state.drawCursor = true;
|
2020-08-20 04:52:24 +00:00
|
|
|
if (state.warpState == WARP_STATE_ARMED)
|
|
|
|
state.warpState = WARP_STATE_ON;
|
2020-01-10 16:41:44 +00:00
|
|
|
}
|
|
|
|
|
2020-01-10 18:22:12 +00:00
|
|
|
int rx = ex - state.curLastX;
|
|
|
|
int ry = ey - state.curLastY;
|
|
|
|
state.curLastX = ex;
|
|
|
|
state.curLastY = ey;
|
2020-01-10 16:41:44 +00:00
|
|
|
|
|
|
|
if (rx == 0 && ry == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (params.scaleMouseInput && !state.serverMode)
|
|
|
|
{
|
|
|
|
state.accX += (float)rx * state.scaleX;
|
|
|
|
state.accY += (float)ry * state.scaleY;
|
|
|
|
rx = floor(state.accX);
|
|
|
|
ry = floor(state.accY);
|
|
|
|
state.accX -= rx;
|
|
|
|
state.accY -= ry;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (state.serverMode && state.mouseSens != 0)
|
|
|
|
{
|
|
|
|
state.sensX += ((float)rx / 10.0f) * (state.mouseSens + 10);
|
|
|
|
state.sensY += ((float)ry / 10.0f) * (state.mouseSens + 10);
|
|
|
|
rx = floor(state.sensX);
|
|
|
|
ry = floor(state.sensY);
|
|
|
|
state.sensX -= rx;
|
|
|
|
state.sensY -= ry;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!spice_mouse_motion(rx, ry))
|
|
|
|
DEBUG_ERROR("failed to send mouse motion message");
|
|
|
|
}
|
|
|
|
|
2020-01-10 18:22:12 +00:00
|
|
|
static void alignMouseWithGuest()
|
|
|
|
{
|
|
|
|
if (state.ignoreInput || !params.useSpiceInput)
|
|
|
|
return;
|
|
|
|
|
2020-08-20 03:46:18 +00:00
|
|
|
state.curLastX = (int)round((float)(state.cursor.x + state.cursor.hx) / state.scaleX) + state.dstRect.x;
|
|
|
|
state.curLastY = (int)round((float)(state.cursor.y + state.cursor.hy) / state.scaleY) + state.dstRect.y;
|
2020-08-20 04:52:24 +00:00
|
|
|
warpMouse(state.curLastX, state.curLastY);
|
2020-01-10 18:22:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void alignMouseWithHost()
|
|
|
|
{
|
|
|
|
if (state.ignoreInput || !params.useSpiceInput)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!state.haveCursorPos || state.serverMode)
|
|
|
|
return;
|
|
|
|
|
2020-08-20 03:46:18 +00:00
|
|
|
state.curLastX = (int)round((float)(state.cursor.x + state.cursor.hx) / state.scaleX) + state.dstRect.x;
|
|
|
|
state.curLastY = (int)round((float)(state.cursor.y + state.cursor.hy) / state.scaleY) + state.dstRect.y;
|
2020-01-10 19:03:16 +00:00
|
|
|
handleMouseMoveEvent(state.curLocalX, state.curLocalY);
|
2020-01-10 18:22:12 +00:00
|
|
|
}
|
|
|
|
|
2020-01-10 16:41:44 +00:00
|
|
|
static void handleResizeEvent(unsigned int w, unsigned int h)
|
|
|
|
{
|
2020-01-10 19:03:16 +00:00
|
|
|
if (state.windowW == w && state.windowH == h)
|
2020-01-10 16:41:44 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
state.windowW = w;
|
|
|
|
state.windowH = h;
|
|
|
|
updatePositionInfo();
|
|
|
|
}
|
|
|
|
|
2020-01-10 19:03:16 +00:00
|
|
|
static void handleWindowLeave()
|
|
|
|
{
|
|
|
|
if (!params.useSpiceInput)
|
|
|
|
return;
|
|
|
|
|
2020-11-15 13:41:22 +00:00
|
|
|
if (!params.alwaysShowCursor)
|
|
|
|
state.drawCursor = false;
|
|
|
|
|
2020-01-11 01:56:46 +00:00
|
|
|
state.cursorInView = false;
|
2020-01-10 19:03:16 +00:00
|
|
|
state.updateCursor = true;
|
2020-08-20 04:52:24 +00:00
|
|
|
state.warpState = WARP_STATE_OFF;
|
2020-01-10 19:03:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void handleWindowEnter()
|
|
|
|
{
|
|
|
|
if (!params.useSpiceInput)
|
|
|
|
return;
|
|
|
|
|
|
|
|
alignMouseWithHost();
|
|
|
|
state.drawCursor = true;
|
|
|
|
state.updateCursor = true;
|
2020-08-20 04:52:24 +00:00
|
|
|
state.warpState = WARP_STATE_ARMED;
|
2020-01-10 19:03:16 +00:00
|
|
|
}
|
|
|
|
|
2020-11-27 23:05:02 +00:00
|
|
|
// only called for X11
|
|
|
|
static void keyboardGrab()
|
|
|
|
{
|
|
|
|
// grab the keyboard so we can intercept WM keys
|
|
|
|
XGrabKeyboard(
|
|
|
|
state.wminfo.info.x11.display,
|
|
|
|
state.wminfo.info.x11.window,
|
|
|
|
true,
|
|
|
|
GrabModeAsync,
|
|
|
|
GrabModeAsync,
|
|
|
|
CurrentTime
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// only called for X11
|
|
|
|
static void keyboardUngrab()
|
|
|
|
{
|
|
|
|
// ungrab the keyboard
|
|
|
|
XUngrabKeyboard(
|
|
|
|
state.wminfo.info.x11.display,
|
|
|
|
CurrentTime
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-01-10 16:41:44 +00:00
|
|
|
int eventFilter(void * userdata, SDL_Event * event)
|
|
|
|
{
|
2018-10-04 07:03:09 +00:00
|
|
|
switch(event->type)
|
2017-10-19 04:15:49 +00:00
|
|
|
{
|
2018-10-04 07:03:09 +00:00
|
|
|
case SDL_QUIT:
|
2017-10-19 04:15:49 +00:00
|
|
|
{
|
2018-10-04 07:03:09 +00:00
|
|
|
if (!params.ignoreQuit)
|
2019-10-26 01:03:10 +00:00
|
|
|
{
|
|
|
|
DEBUG_INFO("Quit event received, exiting...");
|
2020-08-11 05:14:58 +00:00
|
|
|
state.state = APP_STATE_SHUTDOWN;
|
2019-10-26 01:03:10 +00:00
|
|
|
}
|
2018-10-04 07:03:09 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2018-01-24 12:46:11 +00:00
|
|
|
|
2018-10-04 07:03:09 +00:00
|
|
|
case SDL_WINDOWEVENT:
|
|
|
|
{
|
|
|
|
switch(event->window.event)
|
|
|
|
{
|
2020-01-10 18:22:12 +00:00
|
|
|
case SDL_WINDOWEVENT_ENTER:
|
2020-01-10 19:03:16 +00:00
|
|
|
if (state.wminfo.subsystem != SDL_SYSWM_X11)
|
|
|
|
handleWindowEnter();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SDL_WINDOWEVENT_LEAVE:
|
|
|
|
if (state.wminfo.subsystem != SDL_SYSWM_X11)
|
|
|
|
handleWindowLeave();
|
2020-01-10 18:22:12 +00:00
|
|
|
break;
|
|
|
|
|
2018-10-04 07:03:09 +00:00
|
|
|
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
2020-01-03 05:51:24 +00:00
|
|
|
case SDL_WINDOWEVENT_RESIZED:
|
|
|
|
if (state.wminfo.subsystem != SDL_SYSWM_X11)
|
2020-01-10 16:41:44 +00:00
|
|
|
handleResizeEvent(event->window.data1, event->window.data2);
|
2018-10-04 07:03:09 +00:00
|
|
|
break;
|
2019-05-23 08:27:21 +00:00
|
|
|
|
|
|
|
// allow a window close event to close the application even if ignoreQuit is set
|
|
|
|
case SDL_WINDOWEVENT_CLOSE:
|
2020-08-11 05:14:58 +00:00
|
|
|
state.state = APP_STATE_SHUTDOWN;
|
2019-05-23 08:27:21 +00:00
|
|
|
break;
|
2018-10-04 07:03:09 +00:00
|
|
|
}
|
|
|
|
return 0;
|
2017-11-25 06:51:34 +00:00
|
|
|
}
|
2018-01-24 12:46:11 +00:00
|
|
|
|
2019-02-22 17:24:30 +00:00
|
|
|
case SDL_SYSWMEVENT:
|
|
|
|
{
|
2020-01-03 05:51:24 +00:00
|
|
|
// When the window manager forces the window size after calling SDL_SetWindowSize, SDL
|
2020-01-10 16:41:44 +00:00
|
|
|
// ignores this update and caches the incorrect window size. As such all related details
|
|
|
|
// are incorect including mouse movement information as it clips to the old window size.
|
2020-01-03 05:51:24 +00:00
|
|
|
if (state.wminfo.subsystem == SDL_SYSWM_X11)
|
|
|
|
{
|
|
|
|
XEvent xe = event->syswm.msg->msg.x11.event;
|
2020-01-10 16:41:44 +00:00
|
|
|
switch(xe.type)
|
2020-01-03 05:51:24 +00:00
|
|
|
{
|
2020-01-10 16:41:44 +00:00
|
|
|
case ConfigureNotify:
|
|
|
|
handleResizeEvent(xe.xconfigure.width, xe.xconfigure.height);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MotionNotify:
|
|
|
|
handleMouseMoveEvent(xe.xmotion.x, xe.xmotion.y);
|
|
|
|
break;
|
2020-01-10 19:03:16 +00:00
|
|
|
|
|
|
|
case EnterNotify:
|
2020-04-07 04:54:00 +00:00
|
|
|
state.curLocalX = xe.xcrossing.x;
|
|
|
|
state.curLocalY = xe.xcrossing.y;
|
|
|
|
state.haveCurLocal = true;
|
2020-01-10 19:03:16 +00:00
|
|
|
handleWindowEnter();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LeaveNotify:
|
2020-04-07 04:54:00 +00:00
|
|
|
state.curLocalX = xe.xcrossing.x;
|
|
|
|
state.curLocalY = xe.xcrossing.y;
|
|
|
|
state.haveCurLocal = true;
|
2020-01-10 19:03:16 +00:00
|
|
|
handleWindowLeave();
|
|
|
|
break;
|
2020-11-27 23:05:02 +00:00
|
|
|
|
|
|
|
case FocusIn:
|
|
|
|
if (xe.xfocus.mode == NotifyNormal ||
|
|
|
|
xe.xfocus.mode == NotifyUngrab)
|
|
|
|
keyboardGrab();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FocusOut:
|
|
|
|
if (xe.xfocus.mode == NotifyNormal ||
|
|
|
|
xe.xfocus.mode == NotifyWhileGrabbed)
|
|
|
|
keyboardUngrab();
|
|
|
|
break;
|
2020-01-03 05:51:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-25 16:06:53 +00:00
|
|
|
if (params.useSpiceClipboard && state.lgc && state.lgc->wmevent)
|
2019-02-22 17:24:30 +00:00
|
|
|
state.lgc->wmevent(event->syswm.msg);
|
|
|
|
return 0;
|
|
|
|
}
|
2019-02-25 16:06:53 +00:00
|
|
|
|
2018-01-24 12:46:11 +00:00
|
|
|
case SDL_MOUSEMOTION:
|
2020-01-10 16:41:44 +00:00
|
|
|
if (state.wminfo.subsystem != SDL_SYSWM_X11)
|
|
|
|
handleMouseMoveEvent(event->motion.x, event->motion.y);
|
2018-01-24 12:46:11 +00:00
|
|
|
break;
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2018-01-24 12:46:11 +00:00
|
|
|
case SDL_KEYDOWN:
|
|
|
|
{
|
|
|
|
SDL_Scancode sc = event->key.keysym.scancode;
|
2019-03-28 12:23:15 +00:00
|
|
|
if (sc == params.escapeKey)
|
2018-01-24 12:46:11 +00:00
|
|
|
{
|
2019-03-28 12:23:15 +00:00
|
|
|
state.escapeActive = true;
|
2019-05-23 10:31:01 +00:00
|
|
|
state.escapeAction = -1;
|
2019-03-28 12:23:15 +00:00
|
|
|
break;
|
|
|
|
}
|
2017-10-29 02:14:49 +00:00
|
|
|
|
2019-03-28 12:23:15 +00:00
|
|
|
if (state.escapeActive)
|
|
|
|
{
|
|
|
|
state.escapeAction = sc;
|
2017-11-25 06:51:34 +00:00
|
|
|
break;
|
|
|
|
}
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2019-03-28 15:33:09 +00:00
|
|
|
if (state.ignoreInput || !params.useSpiceInput)
|
2019-03-28 15:17:06 +00:00
|
|
|
break;
|
|
|
|
|
2018-01-24 12:46:11 +00:00
|
|
|
uint32_t scancode = mapScancode(sc);
|
|
|
|
if (scancode == 0)
|
2017-11-25 06:51:34 +00:00
|
|
|
break;
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2018-01-29 06:27:12 +00:00
|
|
|
if (!state.keyDown[sc])
|
2017-11-25 06:51:34 +00:00
|
|
|
{
|
2018-01-29 06:27:12 +00:00
|
|
|
if (spice_key_down(scancode))
|
|
|
|
state.keyDown[sc] = true;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("SDL_KEYDOWN: failed to send message");
|
|
|
|
break;
|
|
|
|
}
|
2018-01-24 12:46:11 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2017-12-01 01:45:23 +00:00
|
|
|
|
2018-01-24 12:46:11 +00:00
|
|
|
case SDL_KEYUP:
|
|
|
|
{
|
|
|
|
SDL_Scancode sc = event->key.keysym.scancode;
|
2019-03-28 12:23:15 +00:00
|
|
|
if (state.escapeActive)
|
|
|
|
{
|
2019-05-23 10:31:01 +00:00
|
|
|
if (state.escapeAction == -1)
|
2019-03-28 12:23:15 +00:00
|
|
|
{
|
2019-03-28 15:33:09 +00:00
|
|
|
if (params.useSpiceInput)
|
|
|
|
{
|
2020-01-10 16:41:44 +00:00
|
|
|
state.serverMode = !state.serverMode;
|
|
|
|
SDL_SetWindowGrab(state.window, state.serverMode);
|
|
|
|
DEBUG_INFO("Server Mode: %s", state.serverMode ? "on" : "off");
|
2019-03-28 15:33:09 +00:00
|
|
|
|
2019-03-30 01:26:06 +00:00
|
|
|
app_alert(
|
2020-01-10 16:41:44 +00:00
|
|
|
state.serverMode ? LG_ALERT_SUCCESS : LG_ALERT_WARNING,
|
|
|
|
state.serverMode ? "Capture Enabled" : "Capture Disabled"
|
2019-03-30 01:26:06 +00:00
|
|
|
);
|
2019-03-28 15:33:09 +00:00
|
|
|
|
2020-08-20 04:52:24 +00:00
|
|
|
if (state.serverMode)
|
|
|
|
state.warpState = WARP_STATE_ON;
|
|
|
|
else
|
2020-01-10 16:41:44 +00:00
|
|
|
alignMouseWithGuest();
|
2019-03-28 15:33:09 +00:00
|
|
|
}
|
2019-03-28 12:23:15 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
KeybindHandle handle = state.bindings[sc];
|
|
|
|
if (handle)
|
|
|
|
handle->callback(sc, handle->opaque);
|
|
|
|
}
|
|
|
|
|
2019-05-23 10:31:01 +00:00
|
|
|
if (sc == params.escapeKey)
|
|
|
|
state.escapeActive = false;
|
2019-03-28 12:23:15 +00:00
|
|
|
}
|
2017-12-01 01:45:23 +00:00
|
|
|
|
2019-03-28 15:33:09 +00:00
|
|
|
if (state.ignoreInput || !params.useSpiceInput)
|
2019-03-28 15:17:06 +00:00
|
|
|
break;
|
|
|
|
|
2018-01-24 12:46:11 +00:00
|
|
|
// avoid sending key up events when we didn't send a down
|
2018-01-29 06:27:12 +00:00
|
|
|
if (!state.keyDown[sc])
|
2018-01-24 12:46:11 +00:00
|
|
|
break;
|
2017-11-25 06:51:34 +00:00
|
|
|
|
2018-01-24 12:46:11 +00:00
|
|
|
uint32_t scancode = mapScancode(sc);
|
|
|
|
if (scancode == 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (spice_key_up(scancode))
|
2018-01-29 06:27:12 +00:00
|
|
|
state.keyDown[sc] = false;
|
2018-01-24 12:46:11 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("SDL_KEYUP: failed to send message");
|
2017-11-25 06:51:34 +00:00
|
|
|
break;
|
|
|
|
}
|
2018-01-24 12:46:11 +00:00
|
|
|
break;
|
|
|
|
}
|
2017-10-29 02:14:49 +00:00
|
|
|
|
2018-01-24 12:46:11 +00:00
|
|
|
case SDL_MOUSEWHEEL:
|
2020-01-11 02:11:12 +00:00
|
|
|
if (state.ignoreInput || !params.useSpiceInput || !state.cursorInView)
|
2019-03-28 15:17:06 +00:00
|
|
|
break;
|
|
|
|
|
2018-01-24 12:46:11 +00:00
|
|
|
if (
|
|
|
|
!spice_mouse_press (event->wheel.y == 1 ? 4 : 5) ||
|
|
|
|
!spice_mouse_release(event->wheel.y == 1 ? 4 : 5)
|
2017-11-25 06:51:34 +00:00
|
|
|
)
|
2018-01-24 12:46:11 +00:00
|
|
|
{
|
|
|
|
DEBUG_ERROR("SDL_MOUSEWHEEL: failed to send messages");
|
2017-11-25 06:51:34 +00:00
|
|
|
break;
|
2018-01-24 12:46:11 +00:00
|
|
|
}
|
|
|
|
break;
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2018-01-24 12:46:11 +00:00
|
|
|
case SDL_MOUSEBUTTONDOWN:
|
2020-01-11 02:11:12 +00:00
|
|
|
if (state.ignoreInput || !params.useSpiceInput || !state.cursorInView)
|
2019-03-28 15:17:06 +00:00
|
|
|
break;
|
|
|
|
|
2018-01-24 12:46:11 +00:00
|
|
|
// The SPICE protocol doesn't support more than a standard PS/2 3 button mouse
|
|
|
|
if (event->button.button > 3)
|
2017-11-25 06:51:34 +00:00
|
|
|
break;
|
2018-01-24 12:46:11 +00:00
|
|
|
if (
|
|
|
|
!spice_mouse_position(event->button.x, event->button.y) ||
|
|
|
|
!spice_mouse_press(event->button.button)
|
|
|
|
)
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("SDL_MOUSEBUTTONDOWN: failed to send message");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
2017-11-25 06:51:34 +00:00
|
|
|
|
2018-01-24 12:46:11 +00:00
|
|
|
case SDL_MOUSEBUTTONUP:
|
2020-01-11 02:11:12 +00:00
|
|
|
if (state.ignoreInput || !params.useSpiceInput || !state.cursorInView)
|
2019-03-28 15:17:06 +00:00
|
|
|
break;
|
|
|
|
|
2018-01-24 12:46:11 +00:00
|
|
|
// The SPICE protocol doesn't support more than a standard PS/2 3 button mouse
|
|
|
|
if (event->button.button > 3)
|
2017-11-25 06:51:34 +00:00
|
|
|
break;
|
2018-01-24 12:46:11 +00:00
|
|
|
if (
|
|
|
|
!spice_mouse_position(event->button.x, event->button.y) ||
|
|
|
|
!spice_mouse_release(event->button.button)
|
|
|
|
)
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("SDL_MOUSEBUTTONUP: failed to send message");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
2017-10-19 04:15:49 +00:00
|
|
|
}
|
|
|
|
|
2018-10-04 07:17:46 +00:00
|
|
|
// consume all events
|
2017-10-19 04:15:49 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-04-13 23:15:03 +00:00
|
|
|
void int_handler(int signal)
|
2017-12-17 09:09:47 +00:00
|
|
|
{
|
|
|
|
switch(signal)
|
|
|
|
{
|
|
|
|
case SIGINT:
|
2019-04-13 23:15:03 +00:00
|
|
|
case SIGTERM:
|
|
|
|
DEBUG_INFO("Caught signal, shutting down...");
|
2020-08-11 05:14:58 +00:00
|
|
|
state.state = APP_STATE_SHUTDOWN;
|
2017-12-17 09:09:47 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-17 12:15:18 +00:00
|
|
|
static bool try_renderer(const int index, const LG_RendererParams lgrParams, Uint32 * sdlFlags)
|
|
|
|
{
|
2019-05-21 05:03:59 +00:00
|
|
|
const LG_Renderer *r = LG_Renderers[index];
|
2017-12-17 12:15:18 +00:00
|
|
|
|
|
|
|
if (!IS_LG_RENDERER_VALID(r))
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("FIXME: Renderer %d is invalid, skipping", index);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// create the renderer
|
|
|
|
state.lgrData = NULL;
|
|
|
|
if (!r->create(&state.lgrData, lgrParams))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// initialize the renderer
|
|
|
|
if (!r->initialize(state.lgrData, sdlFlags))
|
|
|
|
{
|
|
|
|
r->deinitialize(state.lgrData);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
DEBUG_INFO("Using Renderer: %s", r->get_name());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-03-28 15:17:06 +00:00
|
|
|
static void toggle_fullscreen(SDL_Scancode key, void * opaque)
|
2019-03-28 15:06:37 +00:00
|
|
|
{
|
|
|
|
SDL_SetWindowFullscreen(state.window, params.fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP);
|
|
|
|
params.fullscreen = !params.fullscreen;
|
|
|
|
}
|
|
|
|
|
2020-10-14 08:32:21 +00:00
|
|
|
static void toggle_video(SDL_Scancode key, void * opaque)
|
|
|
|
{
|
|
|
|
state.stopVideo = !state.stopVideo;
|
|
|
|
app_alert(
|
|
|
|
LG_ALERT_INFO,
|
|
|
|
state.stopVideo ? "Video Stream Disabled" : "Video Stream Enabled"
|
|
|
|
);
|
|
|
|
|
2020-10-14 08:40:20 +00:00
|
|
|
if (!state.stopVideo)
|
2020-10-14 08:32:21 +00:00
|
|
|
{
|
|
|
|
if (t_frame)
|
|
|
|
{
|
|
|
|
lgJoinThread(t_frame, NULL);
|
|
|
|
t_frame = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!lgCreateThread("frameThread", frameThread, NULL, &t_frame))
|
|
|
|
DEBUG_ERROR("frame create thread failed");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-28 15:17:06 +00:00
|
|
|
static void toggle_input(SDL_Scancode key, void * opaque)
|
|
|
|
{
|
|
|
|
state.ignoreInput = !state.ignoreInput;
|
2019-03-30 01:26:06 +00:00
|
|
|
app_alert(
|
|
|
|
LG_ALERT_INFO,
|
|
|
|
state.ignoreInput ? "Input Disabled" : "Input Enabled"
|
|
|
|
);
|
2019-03-28 15:17:06 +00:00
|
|
|
}
|
|
|
|
|
2020-04-22 16:31:30 +00:00
|
|
|
static void quit(SDL_Scancode key, void * opaque)
|
|
|
|
{
|
2020-08-11 05:14:58 +00:00
|
|
|
state.state = APP_STATE_SHUTDOWN;
|
2020-04-22 16:31:30 +00:00
|
|
|
}
|
|
|
|
|
2019-05-23 19:29:38 +00:00
|
|
|
static void mouse_sens_inc(SDL_Scancode key, void * opaque)
|
|
|
|
{
|
|
|
|
char * msg;
|
|
|
|
if (state.mouseSens < 9)
|
|
|
|
++state.mouseSens;
|
|
|
|
|
2019-05-23 19:35:16 +00:00
|
|
|
alloc_sprintf(&msg, "Sensitivity: %s%d", state.mouseSens > 0 ? "+" : "", state.mouseSens);
|
2019-05-23 19:29:38 +00:00
|
|
|
app_alert(
|
|
|
|
LG_ALERT_INFO,
|
|
|
|
msg
|
|
|
|
);
|
|
|
|
free(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void mouse_sens_dec(SDL_Scancode key, void * opaque)
|
|
|
|
{
|
|
|
|
char * msg;
|
|
|
|
|
|
|
|
if (state.mouseSens > -9)
|
|
|
|
--state.mouseSens;
|
|
|
|
|
2019-05-23 19:35:16 +00:00
|
|
|
alloc_sprintf(&msg, "Sensitivity: %s%d", state.mouseSens > 0 ? "+" : "", state.mouseSens);
|
2019-05-23 19:29:38 +00:00
|
|
|
app_alert(
|
|
|
|
LG_ALERT_INFO,
|
|
|
|
msg
|
|
|
|
);
|
|
|
|
free(msg);
|
|
|
|
}
|
|
|
|
|
2019-05-31 06:39:55 +00:00
|
|
|
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 );
|
|
|
|
}
|
|
|
|
|
2019-03-28 15:06:37 +00:00
|
|
|
static void register_key_binds()
|
|
|
|
{
|
2019-05-23 19:29:38 +00:00
|
|
|
state.kbFS = app_register_keybind(SDL_SCANCODE_F , toggle_fullscreen, NULL);
|
2020-10-14 08:32:21 +00:00
|
|
|
state.kbVideo = app_register_keybind(SDL_SCANCODE_V , toggle_video , NULL);
|
2019-05-23 19:29:38 +00:00
|
|
|
state.kbInput = app_register_keybind(SDL_SCANCODE_I , toggle_input , NULL);
|
2020-04-22 16:31:30 +00:00
|
|
|
state.kbQuit = app_register_keybind(SDL_SCANCODE_Q , quit , NULL);
|
2019-05-23 19:29:38 +00:00
|
|
|
state.kbMouseSensInc = app_register_keybind(SDL_SCANCODE_INSERT, mouse_sens_inc , NULL);
|
|
|
|
state.kbMouseSensDec = app_register_keybind(SDL_SCANCODE_DELETE, mouse_sens_dec , NULL);
|
2019-05-31 06:39:55 +00:00
|
|
|
|
|
|
|
state.kbCtrlAltFn[0 ] = app_register_keybind(SDL_SCANCODE_F1 , ctrl_alt_fn, NULL);
|
|
|
|
state.kbCtrlAltFn[1 ] = app_register_keybind(SDL_SCANCODE_F2 , ctrl_alt_fn, NULL);
|
|
|
|
state.kbCtrlAltFn[2 ] = app_register_keybind(SDL_SCANCODE_F3 , ctrl_alt_fn, NULL);
|
|
|
|
state.kbCtrlAltFn[3 ] = app_register_keybind(SDL_SCANCODE_F4 , ctrl_alt_fn, NULL);
|
|
|
|
state.kbCtrlAltFn[4 ] = app_register_keybind(SDL_SCANCODE_F5 , ctrl_alt_fn, NULL);
|
|
|
|
state.kbCtrlAltFn[5 ] = app_register_keybind(SDL_SCANCODE_F6 , ctrl_alt_fn, NULL);
|
|
|
|
state.kbCtrlAltFn[6 ] = app_register_keybind(SDL_SCANCODE_F7 , ctrl_alt_fn, NULL);
|
|
|
|
state.kbCtrlAltFn[7 ] = app_register_keybind(SDL_SCANCODE_F8 , ctrl_alt_fn, NULL);
|
|
|
|
state.kbCtrlAltFn[8 ] = app_register_keybind(SDL_SCANCODE_F9 , ctrl_alt_fn, NULL);
|
|
|
|
state.kbCtrlAltFn[9 ] = app_register_keybind(SDL_SCANCODE_F10, ctrl_alt_fn, NULL);
|
|
|
|
state.kbCtrlAltFn[10] = app_register_keybind(SDL_SCANCODE_F11, ctrl_alt_fn, NULL);
|
|
|
|
state.kbCtrlAltFn[11] = app_register_keybind(SDL_SCANCODE_F12, ctrl_alt_fn, NULL);
|
2019-03-28 15:06:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void release_key_binds()
|
|
|
|
{
|
2020-10-14 08:32:21 +00:00
|
|
|
app_release_keybind(&state.kbFS );
|
|
|
|
app_release_keybind(&state.kbVideo);
|
2019-03-28 15:17:06 +00:00
|
|
|
app_release_keybind(&state.kbInput);
|
2020-10-14 08:32:21 +00:00
|
|
|
app_release_keybind(&state.kbQuit );
|
2020-04-23 08:00:17 +00:00
|
|
|
app_release_keybind(&state.kbMouseSensInc);
|
|
|
|
app_release_keybind(&state.kbMouseSensDec);
|
2019-05-31 06:39:55 +00:00
|
|
|
for(int i = 0; i < 12; ++i)
|
|
|
|
app_release_keybind(&state.kbCtrlAltFn[i]);
|
2019-03-28 15:06:37 +00:00
|
|
|
}
|
|
|
|
|
2019-10-26 01:03:10 +00:00
|
|
|
static int lg_run()
|
2017-10-19 04:15:49 +00:00
|
|
|
{
|
|
|
|
memset(&state, 0, sizeof(state));
|
2020-08-11 05:14:58 +00:00
|
|
|
state.state = APP_STATE_RUNNING;
|
2020-01-03 04:53:44 +00:00
|
|
|
state.scaleX = 1.0f;
|
|
|
|
state.scaleY = 1.0f;
|
|
|
|
state.resizeDone = true;
|
2020-01-10 19:03:16 +00:00
|
|
|
state.drawCursor = true;
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2019-05-23 19:29:38 +00:00
|
|
|
state.mouseSens = params.mouseSens;
|
|
|
|
if (state.mouseSens < -9) state.mouseSens = -9;
|
|
|
|
else if (state.mouseSens > 9) state.mouseSens = 9;
|
|
|
|
|
2018-07-28 20:37:48 +00:00
|
|
|
char* XDG_SESSION_TYPE = getenv("XDG_SESSION_TYPE");
|
2018-10-03 14:09:00 +00:00
|
|
|
|
2019-02-22 11:40:57 +00:00
|
|
|
if (XDG_SESSION_TYPE == NULL)
|
2018-07-31 22:56:50 +00:00
|
|
|
XDG_SESSION_TYPE = "unspecified";
|
2018-10-03 14:09:00 +00:00
|
|
|
|
2019-02-22 11:40:57 +00:00
|
|
|
if (strcmp(XDG_SESSION_TYPE, "wayland") == 0)
|
|
|
|
{
|
2018-07-28 20:37:48 +00:00
|
|
|
DEBUG_INFO("Wayland detected");
|
2019-06-18 23:03:15 +00:00
|
|
|
if (getenv("SDL_VIDEODRIVER") == NULL)
|
2019-02-22 11:40:57 +00:00
|
|
|
{
|
2019-06-18 23:01:28 +00:00
|
|
|
int err = setenv("SDL_VIDEODRIVER", "wayland", 1);
|
|
|
|
if (err < 0)
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("Unable to set the env variable SDL_VIDEODRIVER: %d", err);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
DEBUG_INFO("SDL_VIDEODRIVER has been set to wayland");
|
2018-07-28 20:37:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-26 16:08:54 +00:00
|
|
|
if (SDL_Init(SDL_INIT_VIDEO) < 0)
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("SDL_Init Failed");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2017-12-17 09:09:47 +00:00
|
|
|
// 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
|
2019-04-13 23:15:03 +00:00
|
|
|
signal(SIGINT , int_handler);
|
|
|
|
signal(SIGTERM, int_handler);
|
2017-12-17 09:09:47 +00:00
|
|
|
|
2019-10-26 01:03:10 +00:00
|
|
|
// try map the shared memory
|
2020-01-03 04:17:14 +00:00
|
|
|
if (!ivshmemOpen(&state.shm))
|
2019-10-26 01:03:10 +00:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-08-11 05:14:58 +00:00
|
|
|
while(state.state != APP_STATE_SHUTDOWN && !spice_ready())
|
2020-01-18 19:51:21 +00:00
|
|
|
if (!spice_process(1000))
|
2019-10-26 01:03:10 +00:00
|
|
|
{
|
2020-08-11 05:14:58 +00:00
|
|
|
state.state = APP_STATE_SHUTDOWN;
|
2019-10-26 01:03:10 +00:00
|
|
|
DEBUG_ERROR("Failed to process spice messages");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2020-08-09 06:17:08 +00:00
|
|
|
spice_mouse_mode(true);
|
2020-01-02 12:59:06 +00:00
|
|
|
if (!lgCreateThread("spiceThread", spiceThread, NULL, &t_spice))
|
2019-10-26 01:03:10 +00:00
|
|
|
{
|
|
|
|
DEBUG_ERROR("spice create thread failed");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// select and init a renderer
|
2017-12-14 06:42:16 +00:00
|
|
|
LG_RendererParams lgrParams;
|
2020-07-19 04:29:29 +00:00
|
|
|
lgrParams.showFPS = params.showFPS;
|
|
|
|
lgrParams.quickSplash = params.quickSplash;
|
2017-12-14 06:42:16 +00:00
|
|
|
Uint32 sdlFlags;
|
|
|
|
|
2017-12-17 12:15:18 +00:00
|
|
|
if (params.forceRenderer)
|
2017-12-14 06:42:16 +00:00
|
|
|
{
|
2017-12-17 12:15:18 +00:00
|
|
|
DEBUG_INFO("Trying forced renderer");
|
|
|
|
sdlFlags = 0;
|
|
|
|
if (!try_renderer(params.forceRendererIndex, lgrParams, &sdlFlags))
|
2017-12-14 06:42:16 +00:00
|
|
|
{
|
2017-12-17 12:15:18 +00:00
|
|
|
DEBUG_ERROR("Forced renderer failed to iniailize");
|
|
|
|
return -1;
|
2017-12-14 06:42:16 +00:00
|
|
|
}
|
2017-12-17 12:15:18 +00:00
|
|
|
state.lgr = LG_Renderers[params.forceRendererIndex];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// probe for a a suitable renderer
|
|
|
|
for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i)
|
2017-12-14 06:42:16 +00:00
|
|
|
{
|
2017-12-17 12:15:18 +00:00
|
|
|
sdlFlags = 0;
|
|
|
|
if (try_renderer(i, lgrParams, &sdlFlags))
|
|
|
|
{
|
|
|
|
state.lgr = LG_Renderers[i];
|
|
|
|
break;
|
|
|
|
}
|
2017-12-14 06:42:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!state.lgr)
|
|
|
|
{
|
|
|
|
DEBUG_INFO("Unable to find a suitable renderer");
|
|
|
|
return -1;
|
|
|
|
}
|
2017-12-13 23:30:55 +00:00
|
|
|
|
2019-10-26 01:03:10 +00:00
|
|
|
// all our ducks are in a line, create the window
|
2017-11-25 06:51:34 +00:00
|
|
|
state.window = SDL_CreateWindow(
|
2019-03-13 20:17:24 +00:00
|
|
|
params.windowTitle,
|
2017-11-25 09:16:26 +00:00
|
|
|
params.center ? SDL_WINDOWPOS_CENTERED : params.x,
|
|
|
|
params.center ? SDL_WINDOWPOS_CENTERED : params.y,
|
|
|
|
params.w,
|
|
|
|
params.h,
|
|
|
|
(
|
2017-12-14 06:42:16 +00:00
|
|
|
SDL_WINDOW_SHOWN |
|
2017-12-15 01:36:46 +00:00
|
|
|
(params.fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0) |
|
2017-11-28 08:48:40 +00:00
|
|
|
(params.allowResize ? SDL_WINDOW_RESIZABLE : 0) |
|
2017-12-14 06:42:16 +00:00
|
|
|
(params.borderless ? SDL_WINDOW_BORDERLESS : 0) |
|
2019-05-26 06:30:24 +00:00
|
|
|
(params.maximize ? SDL_WINDOW_MAXIMIZED : 0) |
|
2017-12-14 06:42:16 +00:00
|
|
|
sdlFlags
|
2017-11-25 09:16:26 +00:00
|
|
|
)
|
2017-11-25 06:51:34 +00:00
|
|
|
);
|
|
|
|
|
2019-02-20 21:27:08 +00:00
|
|
|
if (state.window == NULL)
|
|
|
|
{
|
2018-07-28 20:37:48 +00:00
|
|
|
DEBUG_ERROR("Could not create an SDL window: %s\n", SDL_GetError());
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2020-11-27 23:16:11 +00:00
|
|
|
SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS,
|
|
|
|
params.minimizeOnFocusLoss ? "1" : "0");
|
2017-12-15 02:27:53 +00:00
|
|
|
|
2019-05-21 05:03:59 +00:00
|
|
|
if (!params.noScreensaver)
|
2019-05-30 10:54:39 +00:00
|
|
|
{
|
2018-05-28 22:34:52 +00:00
|
|
|
SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1");
|
2019-05-30 10:54:39 +00:00
|
|
|
SDL_EnableScreenSaver();
|
|
|
|
}
|
2018-05-28 22:34:52 +00:00
|
|
|
|
2018-10-03 14:09:00 +00:00
|
|
|
if (!params.center)
|
|
|
|
SDL_SetWindowPosition(state.window, params.x, params.y);
|
|
|
|
|
2018-12-16 09:01:19 +00:00
|
|
|
// ensure the initial window size is stored in the state
|
|
|
|
SDL_GetWindowSize(state.window, &state.windowW, &state.windowH);
|
|
|
|
|
2019-02-20 18:58:07 +00:00
|
|
|
// ensure renderer viewport is aware of the current window size
|
|
|
|
updatePositionInfo();
|
|
|
|
|
2020-11-07 23:58:18 +00:00
|
|
|
if (params.fpsMin <= 0)
|
2020-08-20 05:50:33 +00:00
|
|
|
{
|
2020-10-26 07:09:45 +00:00
|
|
|
// default 30 fps
|
|
|
|
state.frameTime = 1000000000ULL / 30ULL;
|
2020-08-20 05:50:33 +00:00
|
|
|
}
|
2019-08-30 01:36:28 +00:00
|
|
|
else
|
2019-07-09 01:13:24 +00:00
|
|
|
{
|
2020-10-26 07:09:45 +00:00
|
|
|
DEBUG_INFO("Using the FPS minimum from args: %d", params.fpsMin);
|
|
|
|
state.frameTime = 1000000000ULL / (unsigned long long)params.fpsMin;
|
2019-07-09 01:13:24 +00:00
|
|
|
}
|
2019-08-30 01:36:28 +00:00
|
|
|
|
2019-03-28 15:06:37 +00:00
|
|
|
register_key_binds();
|
|
|
|
|
2017-12-10 17:11:36 +00:00
|
|
|
// set the compositor hint to bypass for low latency
|
2020-01-03 05:51:24 +00:00
|
|
|
SDL_VERSION(&state.wminfo.version);
|
|
|
|
if (SDL_GetWindowWMInfo(state.window, &state.wminfo))
|
2017-12-10 17:11:36 +00:00
|
|
|
{
|
2020-01-03 05:51:24 +00:00
|
|
|
if (state.wminfo.subsystem == SDL_SYSWM_X11)
|
2017-12-10 17:11:36 +00:00
|
|
|
{
|
2020-01-13 03:03:26 +00:00
|
|
|
// enable X11 events to work around SDL2 bugs
|
|
|
|
SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
|
|
|
|
|
2020-01-13 11:21:12 +00:00
|
|
|
#if SDL_VIDEO_DRIVER_X11_XINPUT2
|
|
|
|
// SDL2 bug, using xinput2 disables all motion notify events
|
|
|
|
// we really don't care about touch, so turn it off and go back
|
|
|
|
// to the default behaiovur.
|
|
|
|
XIEventMask xinputmask =
|
|
|
|
{
|
|
|
|
.deviceid = XIAllMasterDevices,
|
|
|
|
.mask = 0,
|
|
|
|
.mask_len = 0
|
|
|
|
};
|
|
|
|
|
|
|
|
XISelectEvents(
|
|
|
|
state.wminfo.info.x11.display,
|
|
|
|
state.wminfo.info.x11.window,
|
|
|
|
&xinputmask,
|
|
|
|
1
|
|
|
|
);
|
|
|
|
#endif
|
|
|
|
|
2017-12-10 17:11:36 +00:00
|
|
|
Atom NETWM_BYPASS_COMPOSITOR = XInternAtom(
|
2020-01-03 05:51:24 +00:00
|
|
|
state.wminfo.info.x11.display,
|
2017-12-10 17:11:36 +00:00
|
|
|
"NETWM_BYPASS_COMPOSITOR",
|
|
|
|
False);
|
|
|
|
|
|
|
|
unsigned long value = 1;
|
|
|
|
XChangeProperty(
|
2020-01-03 05:51:24 +00:00
|
|
|
state.wminfo.info.x11.display,
|
|
|
|
state.wminfo.info.x11.window,
|
2017-12-10 17:11:36 +00:00
|
|
|
NETWM_BYPASS_COMPOSITOR,
|
|
|
|
XA_CARDINAL,
|
|
|
|
32,
|
|
|
|
PropModeReplace,
|
|
|
|
(unsigned char *)&value,
|
|
|
|
1
|
|
|
|
);
|
2019-02-22 17:24:30 +00:00
|
|
|
|
2019-03-28 08:56:14 +00:00
|
|
|
state.lgc = LG_Clipboards[0];
|
2017-12-10 17:11:36 +00:00
|
|
|
}
|
2018-07-28 20:37:48 +00:00
|
|
|
} else {
|
|
|
|
DEBUG_ERROR("Could not get SDL window information %s", SDL_GetError());
|
|
|
|
return -1;
|
2017-12-10 17:11:36 +00:00
|
|
|
}
|
|
|
|
|
2019-02-22 17:24:30 +00:00
|
|
|
if (state.lgc)
|
|
|
|
{
|
|
|
|
DEBUG_INFO("Using Clipboard: %s", state.lgc->getName());
|
2020-01-03 05:51:24 +00:00
|
|
|
if (!state.lgc->init(&state.wminfo, clipboardRelease, clipboardNotify, clipboardData))
|
2019-02-22 17:24:30 +00:00
|
|
|
{
|
|
|
|
DEBUG_WARN("Failed to initialize the clipboard interface, continuing anyway");
|
|
|
|
state.lgc = NULL;
|
|
|
|
}
|
2019-02-24 18:43:18 +00:00
|
|
|
|
|
|
|
state.cbRequestList = ll_new();
|
2019-02-22 17:24:30 +00:00
|
|
|
}
|
|
|
|
|
2017-12-03 08:53:30 +00:00
|
|
|
if (params.hideMouse)
|
|
|
|
{
|
|
|
|
// work around SDL_ShowCursor being non functional
|
|
|
|
int32_t cursorData[2] = {0, 0};
|
|
|
|
cursor = SDL_CreateCursor((uint8_t*)cursorData, (uint8_t*)cursorData, 8, 8, 4, 4);
|
|
|
|
SDL_SetCursor(cursor);
|
|
|
|
SDL_ShowCursor(SDL_DISABLE);
|
|
|
|
}
|
2017-10-26 19:31:05 +00:00
|
|
|
|
2020-05-21 01:13:42 +00:00
|
|
|
if (params.captureOnStart)
|
|
|
|
{
|
|
|
|
state.serverMode = true;
|
|
|
|
SDL_SetWindowGrab(state.window, state.serverMode);
|
|
|
|
DEBUG_INFO("Server Mode: %s", state.serverMode ? "on" : "off");
|
|
|
|
}
|
|
|
|
|
2019-12-09 16:30:04 +00:00
|
|
|
// setup the startup condition
|
2020-01-02 13:08:43 +00:00
|
|
|
if (!(e_startup = lgCreateEvent(false, 0)))
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("failed to create the startup event");
|
|
|
|
return -1;
|
|
|
|
}
|
2019-12-09 16:30:04 +00:00
|
|
|
|
2020-05-21 03:41:59 +00:00
|
|
|
// setup the new frame event
|
|
|
|
if (!(e_frame = lgCreateEvent(true, 0)))
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("failed to create the frame event");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2019-10-26 01:03:10 +00:00
|
|
|
// start the renderThread so we don't just display junk
|
2020-01-02 12:59:06 +00:00
|
|
|
if (!lgCreateThread("renderThread", renderThread, NULL, &t_render))
|
2017-10-19 04:15:49 +00:00
|
|
|
{
|
2019-10-26 01:03:10 +00:00
|
|
|
DEBUG_ERROR("render create thread failed");
|
|
|
|
return -1;
|
|
|
|
}
|
2017-11-25 06:51:34 +00:00
|
|
|
|
2019-10-26 01:03:10 +00:00
|
|
|
// ensure mouse acceleration is identical in server mode
|
|
|
|
SDL_SetHintWithPriority(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, "1", SDL_HINT_OVERRIDE);
|
|
|
|
SDL_SetEventFilter(eventFilter, NULL);
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2020-05-17 01:25:27 +00:00
|
|
|
// wait for startup to complete so that any error messages below are output at
|
|
|
|
// the end of the output
|
|
|
|
lgWaitEvent(e_startup, TIMEOUT_INFINITE);
|
|
|
|
|
2020-01-09 09:32:42 +00:00
|
|
|
LGMP_STATUS status;
|
2020-05-20 23:28:41 +00:00
|
|
|
|
2020-08-11 05:14:58 +00:00
|
|
|
while(state.state == APP_STATE_RUNNING)
|
2020-05-20 23:28:41 +00:00
|
|
|
{
|
|
|
|
if ((status = lgmpClientInit(state.shm.mem, state.shm.size, &state.lgmp)) == LGMP_OK)
|
|
|
|
break;
|
|
|
|
|
|
|
|
DEBUG_ERROR("lgmpClientInit Failed: %s", lgmpStatusString(status));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2020-05-20 23:32:08 +00:00
|
|
|
/* 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);
|
|
|
|
|
2020-05-17 01:54:07 +00:00
|
|
|
uint32_t udataSize;
|
|
|
|
KVMFR *udata;
|
2020-08-11 04:45:43 +00:00
|
|
|
int waitCount = 0;
|
2020-05-17 01:13:08 +00:00
|
|
|
|
2020-08-11 04:45:43 +00:00
|
|
|
restart:
|
2020-08-11 05:14:58 +00:00
|
|
|
while(state.state == APP_STATE_RUNNING)
|
2020-05-17 01:54:07 +00:00
|
|
|
{
|
2020-05-20 23:28:41 +00:00
|
|
|
if ((status = lgmpClientSessionInit(state.lgmp, &udataSize, (uint8_t **)&udata)) == LGMP_OK)
|
2020-01-09 09:32:42 +00:00
|
|
|
break;
|
2018-05-28 05:30:04 +00:00
|
|
|
|
2020-05-20 23:28:41 +00:00
|
|
|
if (status != LGMP_ERR_INVALID_SESSION && status != LGMP_ERR_INVALID_MAGIC)
|
2020-01-09 09:32:42 +00:00
|
|
|
{
|
2020-05-20 23:28:41 +00:00
|
|
|
DEBUG_ERROR("lgmpClientSessionInit Failed: %s", lgmpStatusString(status));
|
|
|
|
return -1;
|
|
|
|
}
|
2020-05-17 01:54:07 +00:00
|
|
|
|
2020-05-20 23:28:41 +00:00
|
|
|
if (waitCount++ == 0)
|
|
|
|
{
|
|
|
|
DEBUG_BREAK();
|
|
|
|
DEBUG_INFO("The host application seems to not be running");
|
|
|
|
DEBUG_INFO("Waiting for the host application to start...");
|
|
|
|
}
|
2020-05-17 01:54:07 +00:00
|
|
|
|
2020-05-20 23:28:41 +00:00
|
|
|
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...");
|
2020-05-17 01:13:08 +00:00
|
|
|
}
|
|
|
|
|
2020-05-20 23:28:41 +00:00
|
|
|
SDL_WaitEventTimeout(NULL, 1000);
|
2019-10-26 01:03:10 +00:00
|
|
|
}
|
2017-12-13 23:08:47 +00:00
|
|
|
|
2020-08-11 05:14:58 +00:00
|
|
|
if (state.state != APP_STATE_RUNNING)
|
2019-10-26 01:03:10 +00:00
|
|
|
return -1;
|
2020-01-09 09:32:42 +00:00
|
|
|
|
2020-08-11 04:52:22 +00:00
|
|
|
// dont show warnings again after the first startup
|
|
|
|
waitCount = 100;
|
|
|
|
|
2020-05-29 04:14:31 +00:00
|
|
|
const bool magicMatches = memcmp(udata->magic, KVMFR_MAGIC, sizeof(udata->magic)) == 0;
|
|
|
|
if (udataSize != sizeof(KVMFR) || !magicMatches || udata->version != KVMFR_VERSION)
|
2020-05-17 01:54:07 +00:00
|
|
|
{
|
|
|
|
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");
|
2020-05-29 04:14:31 +00:00
|
|
|
DEBUG_ERROR("Please install the matching host application for this client");
|
|
|
|
|
|
|
|
if (magicMatches)
|
2020-05-29 04:24:06 +00:00
|
|
|
{
|
2020-05-29 04:14:31 +00:00
|
|
|
DEBUG_ERROR("Expected KVMFR version %d, got %d", KVMFR_VERSION, udata->version);
|
2020-05-29 04:24:06 +00:00
|
|
|
if (udata->version >= 2)
|
|
|
|
DEBUG_ERROR("Host version: %s", udata->hostver);
|
|
|
|
}
|
2020-05-29 04:14:31 +00:00
|
|
|
else
|
|
|
|
DEBUG_ERROR("Invalid KVMFR magic");
|
|
|
|
|
2020-05-17 01:54:07 +00:00
|
|
|
DEBUG_BREAK();
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2020-05-29 04:14:31 +00:00
|
|
|
DEBUG_INFO("Host ready, reported version: %s", udata->hostver);
|
|
|
|
DEBUG_INFO("Starting session");
|
2017-12-19 13:53:45 +00:00
|
|
|
|
2020-01-02 12:59:06 +00:00
|
|
|
if (!lgCreateThread("cursorThread", cursorThread, NULL, &t_cursor))
|
2019-12-09 16:30:04 +00:00
|
|
|
{
|
|
|
|
DEBUG_ERROR("cursor create thread failed");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2020-01-02 12:59:06 +00:00
|
|
|
if (!lgCreateThread("frameThread", frameThread, NULL, &t_frame))
|
2019-10-26 01:03:10 +00:00
|
|
|
{
|
|
|
|
DEBUG_ERROR("frame create thread failed");
|
|
|
|
return -1;
|
|
|
|
}
|
2017-12-19 13:53:45 +00:00
|
|
|
|
2020-08-11 05:14:58 +00:00
|
|
|
while(state.state == APP_STATE_RUNNING)
|
2019-10-26 01:03:10 +00:00
|
|
|
{
|
2020-01-09 09:32:42 +00:00
|
|
|
if (!lgmpClientSessionValid(state.lgmp))
|
|
|
|
{
|
2020-08-11 05:14:58 +00:00
|
|
|
state.state = APP_STATE_RESTART;
|
2020-04-12 03:48:59 +00:00
|
|
|
break;
|
2020-01-09 09:32:42 +00:00
|
|
|
}
|
2020-08-11 04:52:22 +00:00
|
|
|
SDL_WaitEventTimeout(NULL, 100);
|
2017-10-19 04:15:49 +00:00
|
|
|
}
|
|
|
|
|
2020-08-11 05:14:58 +00:00
|
|
|
if (state.state == APP_STATE_RESTART)
|
2020-08-11 04:30:44 +00:00
|
|
|
{
|
|
|
|
lgSignalEvent(e_startup);
|
|
|
|
lgSignalEvent(e_frame);
|
|
|
|
lgJoinThread(t_frame , NULL);
|
|
|
|
lgJoinThread(t_cursor, NULL);
|
2020-08-11 04:45:43 +00:00
|
|
|
t_frame = NULL;
|
|
|
|
t_cursor = NULL;
|
|
|
|
|
2020-08-11 05:14:58 +00:00
|
|
|
state.state = APP_STATE_RUNNING;
|
2020-08-11 04:30:44 +00:00
|
|
|
state.lgr->on_restart(state.lgrData);
|
|
|
|
|
|
|
|
DEBUG_INFO("Waiting for the host to restart...");
|
|
|
|
goto restart;
|
|
|
|
}
|
|
|
|
|
2019-10-26 01:03:10 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void lg_shutdown()
|
|
|
|
{
|
2020-08-11 05:14:58 +00:00
|
|
|
state.state = APP_STATE_SHUTDOWN;
|
2018-01-29 06:27:12 +00:00
|
|
|
if (t_render)
|
2020-01-27 01:25:47 +00:00
|
|
|
{
|
|
|
|
lgSignalEvent(e_startup);
|
2020-05-21 03:41:59 +00:00
|
|
|
lgSignalEvent(e_frame);
|
2020-01-02 12:59:06 +00:00
|
|
|
lgJoinThread(t_render, NULL);
|
2020-01-27 01:25:47 +00:00
|
|
|
}
|
2018-01-29 06:27:12 +00:00
|
|
|
|
2020-01-09 09:32:42 +00:00
|
|
|
lgmpClientFree(&state.lgmp);
|
|
|
|
|
2020-05-21 03:41:59 +00:00
|
|
|
if (e_frame)
|
|
|
|
{
|
|
|
|
lgFreeEvent(e_frame);
|
|
|
|
e_frame = NULL;
|
|
|
|
}
|
|
|
|
|
2020-01-02 13:08:43 +00:00
|
|
|
if (e_startup)
|
2019-12-09 16:30:04 +00:00
|
|
|
{
|
2020-01-02 13:08:43 +00:00
|
|
|
lgFreeEvent(e_startup);
|
|
|
|
e_startup = NULL;
|
2019-12-09 16:30:04 +00:00
|
|
|
}
|
|
|
|
|
2018-01-29 06:27:12 +00:00
|
|
|
// if spice is still connected send key up events for any pressed keys
|
2019-02-25 16:06:53 +00:00
|
|
|
if (params.useSpiceInput && spice_ready())
|
2018-01-29 06:27:12 +00:00
|
|
|
{
|
|
|
|
for(int i = 0; i < SDL_NUM_SCANCODES; ++i)
|
|
|
|
if (state.keyDown[i])
|
|
|
|
{
|
|
|
|
uint32_t scancode = mapScancode(i);
|
|
|
|
if (scancode == 0)
|
|
|
|
continue;
|
|
|
|
state.keyDown[i] = false;
|
|
|
|
spice_key_up(scancode);
|
|
|
|
}
|
|
|
|
|
2020-02-01 03:24:23 +00:00
|
|
|
spice_disconnect();
|
2018-01-29 06:27:12 +00:00
|
|
|
if (t_spice)
|
2020-01-02 12:59:06 +00:00
|
|
|
lgJoinThread(t_spice, NULL);
|
2018-01-29 06:27:12 +00:00
|
|
|
}
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2019-02-22 11:38:52 +00:00
|
|
|
if (state.lgc)
|
2019-02-24 18:43:18 +00:00
|
|
|
{
|
2019-02-22 11:38:52 +00:00
|
|
|
state.lgc->free();
|
|
|
|
|
2019-02-24 18:43:18 +00:00
|
|
|
struct CBRequest *cbr;
|
|
|
|
while(ll_shift(state.cbRequestList, (void **)&cbr))
|
|
|
|
free(cbr);
|
|
|
|
ll_free(state.cbRequestList);
|
|
|
|
}
|
|
|
|
|
2017-10-19 04:15:49 +00:00
|
|
|
if (state.window)
|
|
|
|
SDL_DestroyWindow(state.window);
|
|
|
|
|
2017-10-26 19:31:05 +00:00
|
|
|
if (cursor)
|
|
|
|
SDL_FreeCursor(cursor);
|
|
|
|
|
2020-01-03 04:17:14 +00:00
|
|
|
ivshmemClose(&state.shm);
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2019-10-26 01:03:10 +00:00
|
|
|
release_key_binds();
|
2017-10-19 04:15:49 +00:00
|
|
|
SDL_Quit();
|
2017-11-25 07:20:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, char * argv[])
|
|
|
|
{
|
2020-05-29 04:18:02 +00:00
|
|
|
if (getuid() == 0)
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("Do not run looking glass as root!");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2020-10-08 15:17:20 +00:00
|
|
|
DEBUG_INFO("Looking Glass (%s)", BUILD_VERSION);
|
2019-10-26 01:03:10 +00:00
|
|
|
DEBUG_INFO("Locking Method: " LG_LOCK_MODE);
|
|
|
|
|
2019-05-26 15:20:05 +00:00
|
|
|
if (!installCrashHandler("/proc/self/exe"))
|
2019-04-11 06:41:52 +00:00
|
|
|
DEBUG_WARN("Failed to install the crash handler");
|
|
|
|
|
2019-05-21 05:03:59 +00:00
|
|
|
config_init();
|
2020-01-03 04:17:14 +00:00
|
|
|
ivshmemOptionsInit();
|
2019-05-21 05:03:59 +00:00
|
|
|
|
|
|
|
// early renderer setup for option registration
|
|
|
|
for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i)
|
|
|
|
LG_Renderers[i]->setup();
|
|
|
|
|
2019-03-30 04:52:00 +00:00
|
|
|
if (!config_load(argc, argv))
|
2017-12-19 00:58:38 +00:00
|
|
|
return -1;
|
2019-03-30 04:52:00 +00:00
|
|
|
|
2018-06-04 17:32:21 +00:00
|
|
|
if (params.grabKeyboard)
|
|
|
|
SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "1");
|
|
|
|
|
2019-10-26 01:03:10 +00:00
|
|
|
const int ret = lg_run();
|
|
|
|
lg_shutdown();
|
2017-12-28 08:55:13 +00:00
|
|
|
|
2019-03-30 05:00:47 +00:00
|
|
|
config_free();
|
2017-12-28 08:55:13 +00:00
|
|
|
return ret;
|
2019-10-26 01:03:10 +00:00
|
|
|
|
2020-01-17 03:35:08 +00:00
|
|
|
}
|