2017-10-31 08:07:16 +00:00
|
|
|
/*
|
2017-12-03 15:22:49 +00:00
|
|
|
Looking Glass - KVM FrameRelay (KVMFR) Client
|
2017-10-31 08:07:16 +00:00
|
|
|
Copyright (C) 2017 Geoffrey McRae <geoff@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
|
|
|
|
*/
|
|
|
|
|
2017-12-07 05:07:25 +00:00
|
|
|
// limit the FPS when sync is turned off
|
|
|
|
#define FPS_LIMIT 240
|
|
|
|
|
2017-11-25 07:20:30 +00:00
|
|
|
#include <getopt.h>
|
2017-10-19 04:15:49 +00:00
|
|
|
#include <SDL2/SDL.h>
|
2017-12-02 05:39:44 +00:00
|
|
|
#include <SDL_ttf.h>
|
2017-10-19 04:15:49 +00:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <sys/stat.h>
|
2017-12-06 10:31:38 +00:00
|
|
|
#include <sys/time.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>
|
|
|
|
|
2017-11-18 23:36:35 +00:00
|
|
|
#define GL_GLEXT_PROTOTYPES
|
2017-10-19 04:15:49 +00:00
|
|
|
#include <GL/gl.h>
|
2017-11-18 23:36:35 +00:00
|
|
|
#include <GL/glu.h>
|
2017-10-19 04:15:49 +00:00
|
|
|
|
|
|
|
#include "debug.h"
|
2017-11-17 03:08:45 +00:00
|
|
|
#include "memcpySSE.h"
|
2017-12-03 15:22:49 +00:00
|
|
|
#include "KVMFR.h"
|
2017-10-19 06:06:42 +00:00
|
|
|
#include "ivshmem/ivshmem.h"
|
2017-10-19 04:18:27 +00:00
|
|
|
#include "spice/spice.h"
|
2017-10-19 04:15:49 +00:00
|
|
|
#include "kb.h"
|
|
|
|
|
2017-12-05 09:33:05 +00:00
|
|
|
#include "lg-renderers.h"
|
|
|
|
|
2017-11-25 06:04:03 +00:00
|
|
|
struct AppState
|
2017-10-19 04:15:49 +00:00
|
|
|
{
|
2017-11-15 05:33:29 +00:00
|
|
|
bool running;
|
|
|
|
bool started;
|
2017-10-29 04:11:37 +00:00
|
|
|
|
2017-12-05 09:33:05 +00:00
|
|
|
TTF_Font *font;
|
|
|
|
SDL_Rect srcRect;
|
|
|
|
LG_RendererRect dstRect;
|
|
|
|
float scaleX, scaleY;
|
2017-12-01 01:45:23 +00:00
|
|
|
|
2017-12-05 09:33:05 +00:00
|
|
|
SDL_Window * window;
|
|
|
|
SDL_Renderer * renderer;
|
2017-12-03 15:22:49 +00:00
|
|
|
struct KVMFRHeader * shm;
|
2017-12-05 09:33:05 +00:00
|
|
|
unsigned int shmSize;
|
2017-10-19 04:15:49 +00:00
|
|
|
};
|
|
|
|
|
2017-11-25 06:51:34 +00:00
|
|
|
struct AppParams
|
|
|
|
{
|
2017-12-02 05:43:42 +00:00
|
|
|
bool vsync;
|
2017-11-25 09:16:26 +00:00
|
|
|
bool autoResize;
|
2017-11-28 08:48:40 +00:00
|
|
|
bool allowResize;
|
2017-11-28 08:16:47 +00:00
|
|
|
bool keepAspect;
|
2017-11-25 06:51:34 +00:00
|
|
|
bool borderless;
|
2017-11-25 09:16:26 +00:00
|
|
|
bool center;
|
|
|
|
int x, y;
|
|
|
|
unsigned int w, h;
|
2017-11-25 06:51:34 +00:00
|
|
|
const char * ivshmemSocket;
|
2017-11-25 10:00:51 +00:00
|
|
|
bool useMipmap;
|
2017-12-02 05:39:44 +00:00
|
|
|
bool showFPS;
|
2017-11-25 06:51:34 +00:00
|
|
|
bool useSpice;
|
2017-11-25 07:20:30 +00:00
|
|
|
const char * spiceHost;
|
|
|
|
unsigned int spicePort;
|
2017-12-01 01:45:23 +00:00
|
|
|
bool scaleMouseInput;
|
2017-12-03 08:53:30 +00:00
|
|
|
bool hideMouse;
|
2017-11-25 06:51:34 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
struct AppState state;
|
|
|
|
struct AppParams params =
|
|
|
|
{
|
2017-12-02 05:43:42 +00:00
|
|
|
.vsync = true,
|
2017-11-25 09:16:26 +00:00
|
|
|
.autoResize = false,
|
2017-11-28 08:48:40 +00:00
|
|
|
.allowResize = true,
|
2017-11-28 08:16:47 +00:00
|
|
|
.keepAspect = true,
|
2017-11-25 09:16:26 +00:00
|
|
|
.borderless = false,
|
|
|
|
.center = true,
|
|
|
|
.x = 0,
|
|
|
|
.y = 0,
|
2017-11-25 09:39:41 +00:00
|
|
|
.w = 1024,
|
|
|
|
.h = 768,
|
2017-11-25 06:51:34 +00:00
|
|
|
.ivshmemSocket = "/tmp/ivshmem_socket",
|
2017-11-28 08:17:56 +00:00
|
|
|
.useMipmap = true,
|
2017-12-02 05:39:44 +00:00
|
|
|
.showFPS = false,
|
2017-11-25 07:20:30 +00:00
|
|
|
.useSpice = true,
|
|
|
|
.spiceHost = "127.0.0.1",
|
2017-12-01 01:45:23 +00:00
|
|
|
.spicePort = 5900,
|
2017-12-03 08:53:30 +00:00
|
|
|
.scaleMouseInput = true,
|
|
|
|
.hideMouse = true
|
2017-11-25 06:51:34 +00:00
|
|
|
};
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2017-12-01 02:42:58 +00:00
|
|
|
inline void updatePositionInfo()
|
|
|
|
{
|
|
|
|
int w, h;
|
|
|
|
SDL_GetRendererOutputSize(state.renderer, &w, &h);
|
|
|
|
|
|
|
|
if (params.keepAspect)
|
|
|
|
{
|
|
|
|
const float srcAspect = (float)state.srcRect.h / (float)state.srcRect.w;
|
|
|
|
const float wndAspect = (float)h / (float)w;
|
|
|
|
if (wndAspect < srcAspect)
|
|
|
|
{
|
|
|
|
state.dstRect.w = (float)h / srcAspect;
|
|
|
|
state.dstRect.h = h;
|
|
|
|
state.dstRect.x = (w >> 1) - (state.dstRect.w >> 1);
|
2017-12-01 02:58:29 +00:00
|
|
|
state.dstRect.y = 0;
|
2017-12-01 02:42:58 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
state.dstRect.w = w;
|
|
|
|
state.dstRect.h = (float)w * srcAspect;
|
2017-12-01 02:58:29 +00:00
|
|
|
state.dstRect.x = 0;
|
2017-12-01 02:42:58 +00:00
|
|
|
state.dstRect.y = (h >> 1) - (state.dstRect.h >> 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
state.dstRect.x = 0;
|
|
|
|
state.dstRect.y = 0;
|
|
|
|
state.dstRect.w = w;
|
|
|
|
state.dstRect.h = h;
|
|
|
|
}
|
|
|
|
|
|
|
|
state.scaleX = (float)state.srcRect.h / (float)state.dstRect.h;
|
|
|
|
state.scaleY = (float)state.srcRect.w / (float)state.dstRect.w;
|
|
|
|
}
|
|
|
|
|
2017-12-03 15:22:49 +00:00
|
|
|
inline bool areFormatsSame(const struct KVMFRHeader s1, const struct KVMFRHeader s2)
|
2017-10-19 04:15:49 +00:00
|
|
|
{
|
|
|
|
return
|
2017-11-15 05:34:37 +00:00
|
|
|
(s1.frameType != FRAME_TYPE_INVALID) &&
|
|
|
|
(s2.frameType != FRAME_TYPE_INVALID) &&
|
2017-10-19 04:15:49 +00:00
|
|
|
(s1.version == s2.version ) &&
|
|
|
|
(s1.frameType == s2.frameType) &&
|
|
|
|
(s1.width == s2.width ) &&
|
|
|
|
(s1.height == s2.height );
|
|
|
|
}
|
|
|
|
|
2017-12-06 10:31:38 +00:00
|
|
|
inline uint64_t microtime()
|
2017-12-03 11:08:43 +00:00
|
|
|
{
|
2017-12-06 10:31:38 +00:00
|
|
|
struct timeval time;
|
|
|
|
gettimeofday(&time, NULL);
|
|
|
|
return ((uint64_t)time.tv_sec * 1000000) + time.tv_usec;
|
2017-12-03 11:08:43 +00:00
|
|
|
}
|
|
|
|
|
2017-12-06 15:37:46 +00:00
|
|
|
uint64_t detectPresentTime()
|
|
|
|
{
|
2017-12-07 05:10:00 +00:00
|
|
|
glFinish();
|
2017-12-06 15:37:46 +00:00
|
|
|
|
2017-12-07 05:10:00 +00:00
|
|
|
// time 20 iterations and compute the average
|
2017-12-06 15:37:46 +00:00
|
|
|
const uint64_t start = microtime();
|
2017-12-07 05:10:00 +00:00
|
|
|
for(int i = 0; i < 20; ++i)
|
|
|
|
{
|
2017-12-06 15:37:46 +00:00
|
|
|
SDL_RenderPresent(state.renderer);
|
2017-12-07 05:10:00 +00:00
|
|
|
glFinish();
|
|
|
|
}
|
|
|
|
const uint64_t t = (microtime() - start) / 20;
|
2017-12-06 15:37:46 +00:00
|
|
|
|
|
|
|
DEBUG_INFO("detected: %lu (%f Hz)", t, 1000000.0f / t);
|
|
|
|
return t;
|
|
|
|
}
|
|
|
|
|
2017-10-19 04:15:49 +00:00
|
|
|
int renderThread(void * unused)
|
|
|
|
{
|
2017-12-05 09:33:05 +00:00
|
|
|
bool error = false;
|
|
|
|
struct KVMFRHeader header;
|
|
|
|
const LG_Renderer * lgr = NULL;
|
|
|
|
void * lgrData;
|
2017-12-05 10:50:24 +00:00
|
|
|
unsigned int frameCount = 0;
|
2017-12-05 09:33:05 +00:00
|
|
|
SDL_Texture * textTexture = NULL;
|
|
|
|
SDL_Rect textRect;
|
2017-11-19 09:19:40 +00:00
|
|
|
|
2017-12-07 05:07:25 +00:00
|
|
|
const uint64_t presentTime =
|
|
|
|
params.vsync ?
|
|
|
|
detectPresentTime() :
|
|
|
|
ceil((1000000.0/(double)FPS_LIMIT));
|
|
|
|
|
|
|
|
unsigned int lateCount = 0;
|
2017-12-06 10:31:38 +00:00
|
|
|
|
2017-12-07 05:07:25 +00:00
|
|
|
int pollDelay = 0;
|
2017-12-06 15:37:46 +00:00
|
|
|
uint64_t drawStart = 0;
|
2017-12-07 05:07:25 +00:00
|
|
|
int drawTime = 0;
|
2017-12-06 10:31:38 +00:00
|
|
|
|
2017-12-07 05:07:25 +00:00
|
|
|
uint64_t fpsStart = microtime();
|
|
|
|
int fpsTime = 0;
|
2017-12-06 10:31:38 +00:00
|
|
|
|
2017-12-07 02:09:07 +00:00
|
|
|
|
2017-12-07 05:07:25 +00:00
|
|
|
volatile uint64_t * dataPos = &state.shm->dataPos;
|
2017-12-06 17:51:54 +00:00
|
|
|
|
2017-12-06 15:37:46 +00:00
|
|
|
while(state.running)
|
|
|
|
{
|
2017-12-07 05:07:25 +00:00
|
|
|
// if the next frame isn't aready available
|
|
|
|
if (header.dataPos == *dataPos)
|
2017-12-06 17:51:54 +00:00
|
|
|
{
|
2017-12-07 05:07:25 +00:00
|
|
|
// wait for a frame
|
|
|
|
const uint64_t pollStart = microtime();
|
|
|
|
if (pollDelay > 0)
|
|
|
|
usleep(pollDelay);
|
2017-12-07 02:09:07 +00:00
|
|
|
|
2017-12-07 05:07:25 +00:00
|
|
|
if (header.dataPos != *dataPos)
|
|
|
|
++lateCount;
|
|
|
|
|
|
|
|
// poll until we have a new frame, or we time out
|
|
|
|
while(header.dataPos == *dataPos && state.running) {
|
|
|
|
if (microtime() - pollStart > 100)
|
2017-12-06 17:51:54 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2017-12-07 05:07:25 +00:00
|
|
|
// update the delay
|
|
|
|
pollDelay = microtime() - pollStart - 100;
|
2017-12-06 10:31:38 +00:00
|
|
|
}
|
|
|
|
|
2017-12-06 15:37:46 +00:00
|
|
|
// sleep for the remainder of the presentation time
|
|
|
|
if (frameCount > 0)
|
|
|
|
{
|
2017-12-07 05:07:25 +00:00
|
|
|
const uint64_t t = microtime();
|
|
|
|
drawTime = t - drawStart;
|
2017-12-06 15:37:46 +00:00
|
|
|
if (drawTime < presentTime)
|
|
|
|
{
|
2017-12-07 05:07:25 +00:00
|
|
|
const uint64_t delta = presentTime - drawTime;
|
2017-12-06 15:37:46 +00:00
|
|
|
if (delta > 1000)
|
|
|
|
usleep(delta - 1000);
|
2017-12-07 05:07:25 +00:00
|
|
|
|
|
|
|
if (!params.vsync)
|
|
|
|
{
|
|
|
|
// poll for the final microsecond
|
|
|
|
const uint64_t target = t + delta;
|
|
|
|
while(microtime() <= target) {}
|
|
|
|
}
|
2017-12-06 15:37:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ensure buffers are flushed
|
|
|
|
glFinish();
|
|
|
|
}
|
2017-12-06 10:31:38 +00:00
|
|
|
|
2017-12-06 15:37:46 +00:00
|
|
|
drawStart = microtime();
|
2017-12-05 10:50:24 +00:00
|
|
|
|
2017-12-05 09:33:05 +00:00
|
|
|
// we must take a copy of the header, both to let the guest advance and to
|
|
|
|
// prevent the contained arguments being abused to overflow buffers
|
|
|
|
memcpy(&header, state.shm, sizeof(struct KVMFRHeader));
|
|
|
|
ivshmem_kick_irq(header.guestID, 0);
|
2017-11-26 02:32:54 +00:00
|
|
|
|
2017-12-05 09:33:05 +00:00
|
|
|
// check the header's magic and version are valid
|
2017-11-28 04:16:51 +00:00
|
|
|
if (
|
2017-12-05 09:33:05 +00:00
|
|
|
memcmp(header.magic, KVMFR_HEADER_MAGIC, sizeof(KVMFR_HEADER_MAGIC)) != 0 ||
|
|
|
|
header.version != KVMFR_HEADER_VERSION
|
2017-11-28 04:16:51 +00:00
|
|
|
)
|
2017-10-26 04:19:48 +00:00
|
|
|
{
|
2017-11-28 04:16:51 +00:00
|
|
|
usleep(1000);
|
|
|
|
continue;
|
2017-10-19 18:10:44 +00:00
|
|
|
}
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2017-12-05 09:33:05 +00:00
|
|
|
// setup the renderer format with the frame format details
|
|
|
|
LG_RendererFormat lgrFormat;
|
|
|
|
lgrFormat.width = header.width;
|
|
|
|
lgrFormat.height = header.height;
|
|
|
|
lgrFormat.stride = header.stride;
|
|
|
|
|
|
|
|
switch(header.frameType)
|
2017-10-19 04:15:49 +00:00
|
|
|
{
|
2017-12-05 09:33:05 +00:00
|
|
|
case FRAME_TYPE_ARGB:
|
|
|
|
lgrFormat.pitch = header.stride * 4;
|
|
|
|
lgrFormat.bpp = 32;
|
|
|
|
break;
|
2017-11-18 23:36:35 +00:00
|
|
|
|
2017-12-05 09:33:05 +00:00
|
|
|
case FRAME_TYPE_RGB:
|
|
|
|
lgrFormat.pitch = header.stride * 3;
|
|
|
|
lgrFormat.bpp = 24;
|
|
|
|
break;
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2017-12-05 09:33:05 +00:00
|
|
|
default:
|
|
|
|
DEBUG_ERROR("Unsupported frameType");
|
|
|
|
error = true;
|
|
|
|
break;
|
|
|
|
}
|
2017-11-18 23:46:11 +00:00
|
|
|
|
2017-12-05 09:33:05 +00:00
|
|
|
if (error)
|
|
|
|
break;
|
2017-11-18 23:46:11 +00:00
|
|
|
|
2017-12-05 09:33:05 +00:00
|
|
|
// check the header's dataPos is sane
|
|
|
|
const size_t dataSize = lgrFormat.height * lgrFormat.pitch;
|
|
|
|
if (header.dataPos + dataSize > state.shmSize)
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("The guest sent an invalid dataPos");
|
|
|
|
break;
|
|
|
|
}
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2017-12-05 09:33:05 +00:00
|
|
|
// check if we have a compatible renderer
|
|
|
|
if (!lgr || !lgr->is_compatible(lgrData, lgrFormat))
|
|
|
|
{
|
|
|
|
LG_RendererParams lgrParams;
|
|
|
|
lgrParams.window = state.window;
|
|
|
|
lgrParams.renderer = state.renderer;
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2017-12-05 09:33:05 +00:00
|
|
|
DEBUG_INFO("Data Format: w=%u, h=%u, s=%u, p=%u, bpp=%u",
|
|
|
|
lgrFormat.width, lgrFormat.height, lgrFormat.stride, lgrFormat.pitch, lgrFormat.bpp);
|
2017-11-25 06:26:26 +00:00
|
|
|
|
2017-12-05 09:33:05 +00:00
|
|
|
// first try to reinitialize any existing renderer
|
|
|
|
if (lgr)
|
|
|
|
{
|
|
|
|
lgr->deinitialize(lgrData);
|
|
|
|
if (lgr->initialize(&lgrData, lgrParams, lgrFormat))
|
2017-11-25 09:21:57 +00:00
|
|
|
{
|
2017-12-05 09:33:05 +00:00
|
|
|
DEBUG_INFO("Reinitialized %s", lgr->get_name());
|
2017-11-25 09:21:57 +00:00
|
|
|
}
|
2017-12-05 09:33:05 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("Failed to reinitialize %s, trying other renderers", lgr->get_name());
|
|
|
|
lgr->deinitialize(lgrData);
|
|
|
|
lgr = NULL;
|
|
|
|
}
|
|
|
|
}
|
2017-11-25 09:21:57 +00:00
|
|
|
|
2017-12-05 09:33:05 +00:00
|
|
|
if (!lgr)
|
|
|
|
{
|
|
|
|
// probe for a a suitable renderer
|
|
|
|
for(const LG_Renderer **r = &LG_Renderers[0]; *r; ++r)
|
2017-11-18 23:36:35 +00:00
|
|
|
{
|
2017-12-05 09:33:05 +00:00
|
|
|
if (!IS_LG_RENDERER_VALID(*r))
|
2017-11-18 23:36:35 +00:00
|
|
|
{
|
2017-12-05 09:33:05 +00:00
|
|
|
DEBUG_ERROR("FIXME: Renderer %d is invalid, skpping", (int)(r - &LG_Renderers[0]));
|
|
|
|
continue;
|
2017-11-18 23:36:35 +00:00
|
|
|
}
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2017-12-05 09:33:05 +00:00
|
|
|
lgrData = NULL;
|
|
|
|
if (!(*r)->initialize(&lgrData, lgrParams, lgrFormat))
|
|
|
|
{
|
|
|
|
(*r)->deinitialize(lgrData);
|
|
|
|
continue;
|
|
|
|
}
|
2017-11-25 06:26:26 +00:00
|
|
|
|
2017-12-05 09:33:05 +00:00
|
|
|
lgr = *r;
|
|
|
|
DEBUG_INFO("Initialized %s", (*r)->get_name());
|
|
|
|
break;
|
2017-12-03 11:10:15 +00:00
|
|
|
}
|
2017-12-05 09:33:05 +00:00
|
|
|
|
|
|
|
if (!lgr)
|
2017-11-25 06:26:26 +00:00
|
|
|
{
|
2017-12-05 09:33:05 +00:00
|
|
|
DEBUG_INFO("Unable to find a suitable renderer");
|
|
|
|
return -1;
|
2017-11-25 06:26:26 +00:00
|
|
|
}
|
2017-11-18 23:36:35 +00:00
|
|
|
}
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2017-12-01 02:42:58 +00:00
|
|
|
state.srcRect.x = 0;
|
|
|
|
state.srcRect.y = 0;
|
|
|
|
state.srcRect.w = header.width;
|
|
|
|
state.srcRect.h = header.height;
|
|
|
|
updatePositionInfo();
|
2017-10-19 04:15:49 +00:00
|
|
|
}
|
|
|
|
|
2017-12-05 09:33:05 +00:00
|
|
|
if (!lgr->render(
|
|
|
|
lgrData,
|
|
|
|
state.dstRect,
|
|
|
|
(uint8_t *)state.shm + header.dataPos,
|
|
|
|
params.useMipmap
|
|
|
|
))
|
2017-11-25 09:21:57 +00:00
|
|
|
{
|
2017-12-05 09:33:05 +00:00
|
|
|
DEBUG_ERROR("Failed to render the frame");
|
2017-11-25 09:21:57 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2017-12-06 15:37:46 +00:00
|
|
|
if (params.showFPS)
|
2017-11-18 23:36:35 +00:00
|
|
|
{
|
2017-12-06 15:37:46 +00:00
|
|
|
// for now render the frame counter here, we really should
|
|
|
|
// move this into the renderers though.
|
|
|
|
if (fpsTime > 1000000)
|
2017-12-02 05:39:44 +00:00
|
|
|
{
|
2017-12-06 15:37:46 +00:00
|
|
|
SDL_Surface *textSurface = NULL;
|
|
|
|
if (textTexture)
|
|
|
|
{
|
|
|
|
SDL_DestroyTexture(textTexture);
|
|
|
|
textTexture = NULL;
|
|
|
|
}
|
2017-12-02 05:39:44 +00:00
|
|
|
|
2017-12-07 02:09:07 +00:00
|
|
|
char str[128];
|
2017-12-06 15:37:46 +00:00
|
|
|
const float avgFPS = 1000.0f / (((float)fpsTime / frameCount) / 1000.0f);
|
2017-12-07 05:07:25 +00:00
|
|
|
snprintf(str, sizeof(str), "FPS: %8.4f (Frames: %d, Late: %d)", avgFPS, frameCount, lateCount);
|
2017-12-06 15:37:46 +00:00
|
|
|
SDL_Color color = {0xff, 0xff, 0xff};
|
|
|
|
if (!(textSurface = TTF_RenderText_Blended(state.font, str, color)))
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("Failed to render text");
|
|
|
|
break;
|
|
|
|
}
|
2017-11-25 06:26:26 +00:00
|
|
|
|
2017-12-06 15:37:46 +00:00
|
|
|
textRect.x = 5;
|
|
|
|
textRect.y = 5;
|
|
|
|
textRect.w = textSurface->w;
|
|
|
|
textRect.h = textSurface->h;
|
2017-11-25 06:26:26 +00:00
|
|
|
|
2017-12-06 15:37:46 +00:00
|
|
|
textTexture = SDL_CreateTextureFromSurface(state.renderer, textSurface);
|
|
|
|
SDL_SetTextureBlendMode(textTexture, SDL_BLENDMODE_BLEND);
|
|
|
|
SDL_FreeSurface(textSurface);
|
2017-12-05 09:33:05 +00:00
|
|
|
|
2017-12-06 15:37:46 +00:00
|
|
|
fpsTime = 0;
|
|
|
|
frameCount = 0;
|
2017-12-07 05:07:25 +00:00
|
|
|
lateCount = 0;
|
2017-12-06 15:37:46 +00:00
|
|
|
}
|
2017-11-18 23:36:35 +00:00
|
|
|
|
2017-12-06 15:37:46 +00:00
|
|
|
if (textTexture)
|
|
|
|
{
|
|
|
|
glEnable(GL_BLEND);
|
|
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
|
|
|
|
|
|
glColor4f(0.0f, 0.0f, 1.0f, 0.5f);
|
|
|
|
glBegin(GL_TRIANGLE_STRIP);
|
|
|
|
glVertex2i(textRect.x , textRect.y );
|
|
|
|
glVertex2i(textRect.x + textRect.w, textRect.y );
|
|
|
|
glVertex2i(textRect.x , textRect.y + textRect.h);
|
|
|
|
glVertex2i(textRect.x + textRect.w, textRect.y + textRect.h);
|
|
|
|
glEnd();
|
|
|
|
|
|
|
|
|
|
|
|
float tw, th;
|
|
|
|
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
|
|
|
SDL_GL_BindTexture(textTexture, &tw, &th);
|
|
|
|
glBegin(GL_TRIANGLE_STRIP);
|
|
|
|
glTexCoord2f(0.0f, 0.0f); glVertex2i(textRect.x , textRect.y );
|
|
|
|
glTexCoord2f(tw , 0.0f); glVertex2i(textRect.x + textRect.w, textRect.y );
|
|
|
|
glTexCoord2f(0.0f, th ); glVertex2i(textRect.x , textRect.y + textRect.h);
|
|
|
|
glTexCoord2f(tw , th ); glVertex2i(textRect.x + textRect.w, textRect.y + textRect.h);
|
|
|
|
glEnd();
|
|
|
|
glDisable(GL_BLEND);
|
|
|
|
SDL_GL_UnbindTexture(textTexture);
|
|
|
|
}
|
2017-12-02 05:39:44 +00:00
|
|
|
}
|
|
|
|
|
2017-12-06 15:37:46 +00:00
|
|
|
|
2017-11-15 07:13:55 +00:00
|
|
|
SDL_RenderPresent(state.renderer);
|
2017-12-06 10:31:38 +00:00
|
|
|
|
|
|
|
++frameCount;
|
2017-12-06 15:37:46 +00:00
|
|
|
uint64_t t = microtime();
|
|
|
|
fpsTime += t - fpsStart;
|
|
|
|
fpsStart = t;
|
2017-10-19 04:15:49 +00:00
|
|
|
}
|
|
|
|
|
2017-12-05 09:33:05 +00:00
|
|
|
if (lgr)
|
|
|
|
lgr->deinitialize(lgrData);
|
2017-11-18 23:36:35 +00:00
|
|
|
|
2017-10-19 04:15:49 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-10-19 07:50:42 +00:00
|
|
|
int ivshmemThread(void * arg)
|
|
|
|
{
|
|
|
|
while(state.running)
|
|
|
|
if (!ivshmem_process())
|
|
|
|
{
|
|
|
|
if (state.running)
|
|
|
|
{
|
|
|
|
state.running = false;
|
|
|
|
DEBUG_ERROR("failed to process ivshmem messages");
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-10-19 04:15:49 +00:00
|
|
|
int spiceThread(void * arg)
|
|
|
|
{
|
|
|
|
while(state.running)
|
|
|
|
if (!spice_process())
|
|
|
|
{
|
2017-10-19 07:50:42 +00:00
|
|
|
if (state.running)
|
|
|
|
{
|
|
|
|
state.running = false;
|
|
|
|
DEBUG_ERROR("failed to process spice messages");
|
|
|
|
}
|
2017-10-19 04:15:49 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
spice_disconnect();
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
int eventThread(void * arg)
|
|
|
|
{
|
2017-11-17 08:57:13 +00:00
|
|
|
bool serverMode = false;
|
|
|
|
bool realignGuest = true;
|
2017-11-25 12:24:29 +00:00
|
|
|
bool keyDown[SDL_NUM_SCANCODES];
|
|
|
|
|
|
|
|
memset(keyDown, 0, sizeof(keyDown));
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2017-10-26 19:38:48 +00:00
|
|
|
// ensure mouse acceleration is identical in server mode
|
|
|
|
SDL_SetHintWithPriority(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, "1", SDL_HINT_OVERRIDE);
|
|
|
|
|
2017-10-19 04:15:49 +00:00
|
|
|
while(state.running)
|
|
|
|
{
|
2017-10-29 04:01:31 +00:00
|
|
|
SDL_Event event;
|
2017-12-03 13:47:20 +00:00
|
|
|
if (!SDL_PollEvent(&event))
|
2017-10-19 04:15:49 +00:00
|
|
|
{
|
2017-12-03 13:47:20 +00:00
|
|
|
usleep(1000);
|
2017-11-25 12:24:29 +00:00
|
|
|
continue;
|
2017-11-25 06:51:34 +00:00
|
|
|
}
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2017-11-25 06:51:34 +00:00
|
|
|
if (event.type == SDL_QUIT)
|
|
|
|
{
|
|
|
|
state.running = false;
|
|
|
|
break;
|
|
|
|
}
|
2017-10-29 04:01:31 +00:00
|
|
|
|
2017-11-25 07:20:30 +00:00
|
|
|
if (!params.useSpice)
|
2017-11-25 06:51:34 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
switch(event.type)
|
|
|
|
{
|
|
|
|
case SDL_KEYDOWN:
|
2017-10-29 04:01:31 +00:00
|
|
|
{
|
2017-11-25 06:51:34 +00:00
|
|
|
SDL_Scancode sc = event.key.keysym.scancode;
|
|
|
|
if (sc == SDL_SCANCODE_SCROLLLOCK)
|
2017-10-19 04:15:49 +00:00
|
|
|
{
|
2017-11-25 06:51:34 +00:00
|
|
|
if (event.key.repeat)
|
2017-10-19 04:15:49 +00:00
|
|
|
break;
|
|
|
|
|
2017-11-25 06:51:34 +00:00
|
|
|
serverMode = !serverMode;
|
|
|
|
spice_mouse_mode(serverMode);
|
|
|
|
SDL_SetRelativeMouseMode(serverMode);
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2017-11-25 06:51:34 +00:00
|
|
|
if (!serverMode)
|
|
|
|
realignGuest = true;
|
2017-10-19 04:15:49 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2017-11-25 06:51:34 +00:00
|
|
|
uint32_t scancode = mapScancode(sc);
|
|
|
|
if (scancode == 0)
|
|
|
|
break;
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2017-11-25 12:24:29 +00:00
|
|
|
if (spice_key_down(scancode))
|
|
|
|
keyDown[sc] = true;
|
|
|
|
else
|
2017-11-25 06:51:34 +00:00
|
|
|
{
|
|
|
|
DEBUG_ERROR("SDL_KEYDOWN: failed to send message");
|
2017-10-19 04:15:49 +00:00
|
|
|
break;
|
|
|
|
}
|
2017-11-25 06:51:34 +00:00
|
|
|
break;
|
|
|
|
}
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2017-11-25 06:51:34 +00:00
|
|
|
case SDL_KEYUP:
|
|
|
|
{
|
|
|
|
SDL_Scancode sc = event.key.keysym.scancode;
|
|
|
|
if (sc == SDL_SCANCODE_SCROLLLOCK)
|
2017-10-19 04:15:49 +00:00
|
|
|
break;
|
|
|
|
|
2017-11-25 12:24:29 +00:00
|
|
|
// avoid sending key up events when we didn't send a down
|
|
|
|
if (!keyDown[sc])
|
|
|
|
break;
|
2017-10-29 02:14:49 +00:00
|
|
|
|
2017-11-25 06:51:34 +00:00
|
|
|
uint32_t scancode = mapScancode(sc);
|
|
|
|
if (scancode == 0)
|
|
|
|
break;
|
|
|
|
|
2017-11-25 12:24:29 +00:00
|
|
|
if (spice_key_up(scancode))
|
|
|
|
keyDown[sc] = false;
|
|
|
|
else
|
2017-11-25 06:51:34 +00:00
|
|
|
{
|
|
|
|
DEBUG_ERROR("SDL_KEYUP: failed to send message");
|
2017-10-19 04:15:49 +00:00
|
|
|
break;
|
2017-10-29 02:14:49 +00:00
|
|
|
}
|
2017-11-25 06:51:34 +00:00
|
|
|
break;
|
|
|
|
}
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2017-11-25 06:51:34 +00:00
|
|
|
case SDL_MOUSEWHEEL:
|
|
|
|
if (
|
|
|
|
!spice_mouse_press (event.wheel.y == 1 ? 4 : 5) ||
|
|
|
|
!spice_mouse_release(event.wheel.y == 1 ? 4 : 5)
|
2017-10-29 02:14:49 +00:00
|
|
|
)
|
2017-11-25 06:51:34 +00:00
|
|
|
{
|
|
|
|
DEBUG_ERROR("SDL_MOUSEWHEEL: failed to send messages");
|
2017-10-19 04:15:49 +00:00
|
|
|
break;
|
2017-11-25 06:51:34 +00:00
|
|
|
}
|
|
|
|
break;
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2017-11-25 06:51:34 +00:00
|
|
|
case SDL_MOUSEMOTION:
|
|
|
|
{
|
2017-12-01 01:45:23 +00:00
|
|
|
if (
|
2017-12-03 13:12:44 +00:00
|
|
|
!serverMode && (
|
|
|
|
event.motion.x < state.dstRect.x ||
|
|
|
|
event.motion.x > state.dstRect.x + state.dstRect.w ||
|
|
|
|
event.motion.y < state.dstRect.y ||
|
|
|
|
event.motion.y > state.dstRect.y + state.dstRect.h
|
|
|
|
)
|
2017-12-01 01:45:23 +00:00
|
|
|
)
|
|
|
|
{
|
|
|
|
realignGuest = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2017-11-25 06:51:34 +00:00
|
|
|
int x = 0;
|
|
|
|
int y = 0;
|
2017-12-01 02:42:58 +00:00
|
|
|
if (realignGuest)
|
2017-11-25 06:51:34 +00:00
|
|
|
{
|
2017-12-01 02:42:58 +00:00
|
|
|
x = event.motion.x - state.dstRect.x;
|
|
|
|
y = event.motion.y - state.dstRect.y;
|
2017-12-01 01:45:23 +00:00
|
|
|
if (params.scaleMouseInput)
|
|
|
|
{
|
2017-12-01 02:42:58 +00:00
|
|
|
x = (float)x * state.scaleX;
|
|
|
|
y = (float)y * state.scaleY;
|
2017-12-01 01:45:23 +00:00
|
|
|
}
|
|
|
|
x -= state.shm->mouseX;
|
|
|
|
y -= state.shm->mouseY;
|
2017-12-01 02:42:58 +00:00
|
|
|
realignGuest = false;
|
2017-12-01 01:45:23 +00:00
|
|
|
|
|
|
|
if (!spice_mouse_motion(x, y))
|
|
|
|
DEBUG_ERROR("SDL_MOUSEMOTION: failed to send message");
|
|
|
|
|
|
|
|
break;
|
2017-11-25 06:51:34 +00:00
|
|
|
}
|
|
|
|
|
2017-12-01 01:45:23 +00:00
|
|
|
x = event.motion.xrel;
|
|
|
|
y = event.motion.yrel;
|
2017-11-25 06:51:34 +00:00
|
|
|
if (x != 0 || y != 0)
|
2017-12-01 01:45:23 +00:00
|
|
|
{
|
|
|
|
if (params.scaleMouseInput)
|
|
|
|
{
|
2017-12-01 02:42:58 +00:00
|
|
|
x = (float)x * state.scaleX;
|
|
|
|
y = (float)y * state.scaleY;
|
2017-12-01 01:45:23 +00:00
|
|
|
}
|
2017-11-25 06:51:34 +00:00
|
|
|
if (!spice_mouse_motion(x, y))
|
2017-10-29 02:14:49 +00:00
|
|
|
{
|
2017-11-25 06:51:34 +00:00
|
|
|
DEBUG_ERROR("SDL_MOUSEMOTION: failed to send message");
|
2017-10-29 02:14:49 +00:00
|
|
|
break;
|
|
|
|
}
|
2017-12-01 01:45:23 +00:00
|
|
|
}
|
2017-11-25 06:51:34 +00:00
|
|
|
break;
|
|
|
|
}
|
2017-10-29 02:14:49 +00:00
|
|
|
|
2017-11-25 06:51:34 +00:00
|
|
|
case SDL_MOUSEBUTTONDOWN:
|
|
|
|
if (
|
|
|
|
!spice_mouse_position(event.button.x, event.button.y) ||
|
|
|
|
!spice_mouse_press(event.button.button)
|
|
|
|
)
|
2017-11-17 08:57:13 +00:00
|
|
|
{
|
2017-11-25 06:51:34 +00:00
|
|
|
DEBUG_ERROR("SDL_MOUSEBUTTONDOWN: failed to send message");
|
2017-10-19 04:15:49 +00:00
|
|
|
break;
|
2017-11-17 08:57:13 +00:00
|
|
|
}
|
2017-11-25 06:51:34 +00:00
|
|
|
break;
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2017-11-25 06:51:34 +00:00
|
|
|
case SDL_MOUSEBUTTONUP:
|
|
|
|
if (
|
|
|
|
!spice_mouse_position(event.button.x, event.button.y) ||
|
|
|
|
!spice_mouse_release(event.button.button)
|
|
|
|
)
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("SDL_MOUSEBUTTONUP: failed to send message");
|
2017-10-19 04:15:49 +00:00
|
|
|
break;
|
2017-11-25 06:51:34 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SDL_WINDOWEVENT:
|
|
|
|
{
|
|
|
|
switch(event.window.event)
|
|
|
|
{
|
|
|
|
case SDL_WINDOWEVENT_ENTER:
|
|
|
|
realignGuest = true;
|
|
|
|
break;
|
2017-12-01 02:42:58 +00:00
|
|
|
|
|
|
|
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
|
|
|
updatePositionInfo();
|
|
|
|
realignGuest = true;
|
|
|
|
break;
|
2017-11-25 06:51:34 +00:00
|
|
|
}
|
|
|
|
break;
|
2017-10-19 04:15:49 +00:00
|
|
|
}
|
2017-11-25 06:51:34 +00:00
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
2017-10-19 04:15:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-11-25 07:20:30 +00:00
|
|
|
int run()
|
2017-10-19 04:15:49 +00:00
|
|
|
{
|
|
|
|
memset(&state, 0, sizeof(state));
|
|
|
|
state.running = true;
|
2017-12-01 01:45:23 +00:00
|
|
|
state.scaleX = 1.0f;
|
|
|
|
state.scaleY = 1.0f;
|
2017-10-19 04:15:49 +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-02 06:46:09 +00:00
|
|
|
if (params.showFPS)
|
2017-12-02 05:39:44 +00:00
|
|
|
{
|
2017-12-02 06:46:09 +00:00
|
|
|
if (TTF_Init() < 0)
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("TTL_Init Failed");
|
|
|
|
return -1;
|
|
|
|
}
|
2017-12-02 05:39:44 +00:00
|
|
|
|
2017-12-07 02:09:07 +00:00
|
|
|
state.font = TTF_OpenFont("/usr/share/fonts/truetype/freefont/FreeMono.ttf", 14);
|
2017-12-02 06:46:09 +00:00
|
|
|
if (!state.font)
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("TTL_OpenFont Failed");
|
|
|
|
return -1;
|
|
|
|
}
|
2017-12-02 05:39:44 +00:00
|
|
|
}
|
|
|
|
|
2017-11-25 06:51:34 +00:00
|
|
|
state.window = SDL_CreateWindow(
|
2017-12-03 15:22:49 +00:00
|
|
|
"Looking Glass (Client)",
|
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,
|
|
|
|
(
|
|
|
|
SDL_WINDOW_SHOWN |
|
2017-11-28 08:48:40 +00:00
|
|
|
(params.allowResize ? SDL_WINDOW_RESIZABLE : 0) |
|
|
|
|
(params.borderless ? SDL_WINDOW_BORDERLESS : 0)
|
2017-11-25 09:16:26 +00:00
|
|
|
)
|
2017-11-25 06:51:34 +00:00
|
|
|
);
|
|
|
|
|
2017-10-26 16:08:54 +00:00
|
|
|
if (!state.window)
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("failed to create window");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2017-10-26 19:31:05 +00:00
|
|
|
SDL_Cursor *cursor = NULL;
|
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
|
|
|
|
2017-10-26 16:08:54 +00:00
|
|
|
state.renderer = SDL_CreateRenderer(state.window, -1,
|
2017-12-02 05:43:42 +00:00
|
|
|
SDL_RENDERER_ACCELERATED |
|
|
|
|
(params.vsync ? SDL_RENDERER_PRESENTVSYNC : 0)
|
|
|
|
);
|
2017-10-26 16:08:54 +00:00
|
|
|
|
2017-12-05 09:33:05 +00:00
|
|
|
if (!state.renderer)
|
2017-11-18 23:36:35 +00:00
|
|
|
{
|
2017-12-05 09:33:05 +00:00
|
|
|
DEBUG_ERROR("failed to create renderer");
|
|
|
|
return -1;
|
2017-11-18 23:36:35 +00:00
|
|
|
}
|
|
|
|
|
2017-10-19 07:50:42 +00:00
|
|
|
int shm_fd = 0;
|
|
|
|
SDL_Thread *t_ivshmem = NULL;
|
|
|
|
SDL_Thread *t_spice = NULL;
|
|
|
|
SDL_Thread *t_event = NULL;
|
2017-10-19 04:15:49 +00:00
|
|
|
|
|
|
|
while(1)
|
|
|
|
{
|
2017-11-25 06:51:34 +00:00
|
|
|
if (!ivshmem_connect(params.ivshmemSocket))
|
2017-10-19 04:15:49 +00:00
|
|
|
{
|
2017-10-19 06:06:42 +00:00
|
|
|
DEBUG_ERROR("failed to connect to the ivshmem server");
|
2017-10-19 04:15:49 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2017-10-19 07:50:42 +00:00
|
|
|
if (!(t_ivshmem = SDL_CreateThread(ivshmemThread, "ivshmemThread", NULL)))
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("ivshmem create thread failed");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2017-12-03 15:22:49 +00:00
|
|
|
state.shm = (struct KVMFRHeader *)ivshmem_get_map();
|
2017-10-19 06:06:42 +00:00
|
|
|
if (!state.shm)
|
2017-10-19 04:15:49 +00:00
|
|
|
{
|
2017-10-19 06:06:42 +00:00
|
|
|
DEBUG_ERROR("Failed to map memory");
|
2017-10-19 04:15:49 +00:00
|
|
|
break;
|
|
|
|
}
|
2017-11-25 09:21:57 +00:00
|
|
|
state.shmSize = ivshmem_get_map_size();
|
2017-10-19 14:03:39 +00:00
|
|
|
state.shm->hostID = ivshmem_get_id();
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2017-11-25 06:51:34 +00:00
|
|
|
if (params.useSpice)
|
2017-10-19 04:15:49 +00:00
|
|
|
{
|
2017-11-25 07:20:30 +00:00
|
|
|
if (!spice_connect(params.spiceHost, params.spicePort, ""))
|
2017-11-25 06:51:34 +00:00
|
|
|
{
|
|
|
|
DEBUG_ERROR("Failed to connect to spice server");
|
|
|
|
return 0;
|
|
|
|
}
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2017-11-25 06:51:34 +00:00
|
|
|
while(state.running && !spice_ready())
|
|
|
|
if (!spice_process())
|
|
|
|
{
|
|
|
|
state.running = false;
|
|
|
|
DEBUG_ERROR("Failed to process spice messages");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(t_spice = SDL_CreateThread(spiceThread, "spiceThread", NULL)))
|
2017-10-19 04:15:49 +00:00
|
|
|
{
|
2017-11-25 06:51:34 +00:00
|
|
|
DEBUG_ERROR("spice create thread failed");
|
2017-10-19 04:15:49 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(t_event = SDL_CreateThread(eventThread, "eventThread", NULL)))
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("gpu create thread failed");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
while(state.running)
|
|
|
|
renderThread(NULL);
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
state.running = false;
|
|
|
|
|
|
|
|
if (t_event)
|
|
|
|
SDL_WaitThread(t_event, NULL);
|
|
|
|
|
2017-10-19 07:50:42 +00:00
|
|
|
// this needs to happen here to abort any waiting reads
|
|
|
|
// as ivshmem uses recvmsg which has no timeout
|
|
|
|
ivshmem_disconnect();
|
|
|
|
if (t_ivshmem)
|
|
|
|
SDL_WaitThread(t_ivshmem, NULL);
|
|
|
|
|
2017-10-19 04:15:49 +00:00
|
|
|
if (t_spice)
|
|
|
|
SDL_WaitThread(t_spice, NULL);
|
|
|
|
|
|
|
|
if (state.renderer)
|
|
|
|
SDL_DestroyRenderer(state.renderer);
|
|
|
|
|
|
|
|
if (state.window)
|
|
|
|
SDL_DestroyWindow(state.window);
|
|
|
|
|
2017-10-26 19:31:05 +00:00
|
|
|
if (cursor)
|
|
|
|
SDL_FreeCursor(cursor);
|
|
|
|
|
2017-10-19 04:15:49 +00:00
|
|
|
if (shm_fd)
|
|
|
|
close(shm_fd);
|
|
|
|
|
2017-12-02 05:39:44 +00:00
|
|
|
TTF_Quit();
|
2017-10-19 04:15:49 +00:00
|
|
|
SDL_Quit();
|
|
|
|
return 0;
|
2017-11-25 07:20:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void doHelp(char * app)
|
|
|
|
{
|
2017-11-25 09:16:26 +00:00
|
|
|
char x[8], y[8];
|
|
|
|
snprintf(x, sizeof(x), "%d", params.x);
|
|
|
|
snprintf(y, sizeof(y), "%d", params.y);
|
|
|
|
|
2017-11-25 07:20:30 +00:00
|
|
|
fprintf(stderr,
|
2017-12-03 15:22:49 +00:00
|
|
|
"Looking Glass Client\n"
|
2017-11-25 07:20:30 +00:00
|
|
|
"Usage: %s [OPTION]...\n"
|
|
|
|
"Example: %s -h\n"
|
|
|
|
"\n"
|
2017-11-25 09:16:26 +00:00
|
|
|
" -h Print out this help\n"
|
2017-11-25 10:00:51 +00:00
|
|
|
"\n"
|
2017-11-25 09:16:26 +00:00
|
|
|
" -f PATH Specify the path to the ivshmem socket [current: %s]\n"
|
2017-11-25 10:00:51 +00:00
|
|
|
"\n"
|
2017-11-25 09:16:26 +00:00
|
|
|
" -s Disable spice client\n"
|
|
|
|
" -c HOST Specify the spice host [current: %s]\n"
|
|
|
|
" -p PORT Specify the spice port [current: %d]\n"
|
2017-12-01 01:45:23 +00:00
|
|
|
" -j Disable cursor position scaling\n"
|
2017-12-03 08:53:30 +00:00
|
|
|
" -M Don't hide the host cursor\n"
|
2017-11-25 09:16:26 +00:00
|
|
|
"\n"
|
2017-11-28 08:17:56 +00:00
|
|
|
" -m Disable mipmapping\n"
|
2017-12-02 05:43:42 +00:00
|
|
|
" -v Disable VSync\n"
|
2017-12-02 05:39:44 +00:00
|
|
|
" -k Enable FPS display\n"
|
2017-11-25 10:00:51 +00:00
|
|
|
"\n"
|
2017-11-25 09:16:26 +00:00
|
|
|
" -a Auto resize the window to the guest\n"
|
2017-11-28 08:52:59 +00:00
|
|
|
" -n Don't allow the window to be manually resized\n"
|
2017-11-28 08:16:47 +00:00
|
|
|
" -r Don't maintain the aspect ratio\n"
|
2017-11-25 09:16:26 +00:00
|
|
|
" -d Borderless mode\n"
|
|
|
|
" -x XPOS Initial window X position [current: %s]\n"
|
|
|
|
" -y YPOS Initial window Y position [current: %s]\n"
|
|
|
|
" -w WIDTH Initial window width [current: %u]\n"
|
|
|
|
" -b HEIGHT Initial window height [current: %u]\n"
|
|
|
|
"\n"
|
|
|
|
" -l License information\n"
|
2017-11-25 07:20:30 +00:00
|
|
|
"\n",
|
|
|
|
app,
|
|
|
|
app,
|
|
|
|
params.ivshmemSocket,
|
|
|
|
params.spiceHost,
|
2017-11-25 09:16:26 +00:00
|
|
|
params.spicePort,
|
|
|
|
params.center ? "center" : x,
|
|
|
|
params.center ? "center" : y,
|
|
|
|
params.w,
|
|
|
|
params.h
|
2017-11-25 07:20:30 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
void doLicense()
|
|
|
|
{
|
|
|
|
fprintf(stderr,
|
|
|
|
"\n"
|
2017-12-03 15:22:49 +00:00
|
|
|
"Looking Glass - KVM FrameRelay (KVMFR) Client\n"
|
2017-11-25 07:20:30 +00:00
|
|
|
"Copyright(C) 2017 Geoffrey McRae <geoff@hostfission.com>\n"
|
|
|
|
"\n"
|
|
|
|
"This program is free software; you can redistribute it and / or modify it under\n"
|
|
|
|
"the terms of the GNU General Public License as published by the Free Software\n"
|
|
|
|
"Foundation; either version 2 of the License, or (at your option) any later\n"
|
|
|
|
"version.\n"
|
|
|
|
"\n"
|
|
|
|
"This program is distributed in the hope that it will be useful, but WITHOUT ANY\n"
|
|
|
|
"WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n"
|
|
|
|
"PARTICULAR PURPOSE.See the GNU General Public License for more details.\n"
|
|
|
|
"\n"
|
|
|
|
"You should have received a copy of the GNU General Public License along with\n"
|
|
|
|
"this program; if not, write to the Free Software Foundation, Inc., 59 Temple\n"
|
|
|
|
"Place, Suite 330, Boston, MA 02111 - 1307 USA\n"
|
|
|
|
"\n"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, char * argv[])
|
|
|
|
{
|
|
|
|
int c;
|
2017-12-07 01:57:29 +00:00
|
|
|
while((c = getopt(argc, argv, "hf:sc:p:jMmvkanrdx:y:w:b:l")) != -1)
|
2017-11-25 07:20:30 +00:00
|
|
|
switch(c)
|
|
|
|
{
|
|
|
|
case '?':
|
|
|
|
case 'h':
|
|
|
|
default :
|
|
|
|
doHelp(argv[0]);
|
|
|
|
return (c == 'h') ? 0 : -1;
|
|
|
|
|
|
|
|
case 'f':
|
|
|
|
params.ivshmemSocket = optarg;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 's':
|
|
|
|
params.useSpice = false;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'c':
|
|
|
|
params.spiceHost = optarg;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'p':
|
|
|
|
params.spicePort = atoi(optarg);
|
|
|
|
break;
|
|
|
|
|
2017-12-01 01:45:23 +00:00
|
|
|
case 'j':
|
|
|
|
params.scaleMouseInput = false;
|
|
|
|
break;
|
|
|
|
|
2017-12-03 08:53:30 +00:00
|
|
|
case 'M':
|
|
|
|
params.hideMouse = false;
|
|
|
|
break;
|
|
|
|
|
2017-11-25 10:00:51 +00:00
|
|
|
case 'm':
|
2017-11-28 08:17:56 +00:00
|
|
|
params.useMipmap = false;
|
2017-11-25 10:00:51 +00:00
|
|
|
break;
|
|
|
|
|
2017-12-02 05:43:42 +00:00
|
|
|
case 'v':
|
|
|
|
params.vsync = false;
|
|
|
|
break;
|
|
|
|
|
2017-12-02 05:39:44 +00:00
|
|
|
case 'k':
|
|
|
|
params.showFPS = true;
|
|
|
|
break;
|
|
|
|
|
2017-11-25 09:16:26 +00:00
|
|
|
case 'a':
|
|
|
|
params.autoResize = true;
|
|
|
|
break;
|
|
|
|
|
2017-11-28 08:48:40 +00:00
|
|
|
case 'n':
|
|
|
|
params.allowResize = false;
|
|
|
|
break;
|
|
|
|
|
2017-11-28 08:16:47 +00:00
|
|
|
case 'r':
|
|
|
|
params.keepAspect = false;
|
|
|
|
break;
|
|
|
|
|
2017-11-25 09:16:26 +00:00
|
|
|
case 'd':
|
|
|
|
params.borderless = true;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'x':
|
|
|
|
params.center = false;
|
|
|
|
params.x = atoi(optarg);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'y':
|
|
|
|
params.center = false;
|
|
|
|
params.y = atoi(optarg);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'w':
|
|
|
|
params.w = atoi(optarg);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'b':
|
|
|
|
params.h = atoi(optarg);
|
|
|
|
break;
|
|
|
|
|
2017-11-25 07:20:30 +00:00
|
|
|
case 'l':
|
|
|
|
doLicense();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return run();
|
2017-10-19 04:15:49 +00:00
|
|
|
}
|