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>
|
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
|
|
|
|
*/
|
|
|
|
|
2017-11-25 07:20:30 +00:00
|
|
|
#include <getopt.h>
|
2017-12-17 09:09:47 +00:00
|
|
|
#include <signal.h>
|
2017-10-19 04:15:49 +00:00
|
|
|
#include <SDL2/SDL.h>
|
2017-12-10 17:11:36 +00:00
|
|
|
#include <SDL2/SDL_syswm.h>
|
|
|
|
#include <SDL2/SDL_ttf.h>
|
2017-10-19 04:15:49 +00:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
2017-12-28 08:55:13 +00:00
|
|
|
#include <pwd.h>
|
2017-10-19 04:15:49 +00:00
|
|
|
#include <sys/stat.h>
|
2017-12-28 04:30:03 +00:00
|
|
|
#include <sys/mman.h>
|
|
|
|
#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>
|
2017-12-28 08:55:13 +00:00
|
|
|
#include <libconfig.h>
|
2017-12-10 06:04:02 +00:00
|
|
|
#include <fontconfig/fontconfig.h>
|
2017-10-19 04:15:49 +00:00
|
|
|
|
|
|
|
#include "debug.h"
|
2017-12-10 14:31:52 +00:00
|
|
|
#include "utils.h"
|
2017-12-03 15:22:49 +00:00
|
|
|
#include "KVMFR.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-12-17 09:16:43 +00:00
|
|
|
bool running;
|
|
|
|
bool started;
|
2017-10-29 04:11:37 +00:00
|
|
|
|
2017-12-17 09:16:43 +00:00
|
|
|
TTF_Font * font;
|
|
|
|
SDL_Point srcSize;
|
|
|
|
LG_RendererRect dstRect;
|
|
|
|
SDL_Point cursor;
|
|
|
|
bool cursorVisible;
|
|
|
|
bool haveCursorPos;
|
|
|
|
float scaleX, scaleY;
|
2017-12-01 01:45:23 +00:00
|
|
|
|
2017-12-17 09:16:43 +00:00
|
|
|
const LG_Renderer * lgr ;
|
|
|
|
void * lgrData;
|
2017-12-10 14:31:52 +00:00
|
|
|
|
2017-12-05 09:33:05 +00:00
|
|
|
SDL_Window * window;
|
2017-12-28 04:30:03 +00:00
|
|
|
int shmFD;
|
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-12-17 11:21:59 +00:00
|
|
|
typedef struct RenderOpts
|
|
|
|
{
|
|
|
|
unsigned int size;
|
|
|
|
unsigned int argc;
|
|
|
|
LG_RendererOptValue * argv;
|
|
|
|
}
|
|
|
|
RendererOpts;
|
|
|
|
|
2017-11-25 06:51:34 +00:00
|
|
|
struct AppParams
|
|
|
|
{
|
2017-12-28 08:55:13 +00:00
|
|
|
const char * configFile;
|
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-12-15 01:36:46 +00:00
|
|
|
bool fullscreen;
|
2017-11-25 09:16:26 +00:00
|
|
|
bool center;
|
|
|
|
int x, y;
|
|
|
|
unsigned int w, h;
|
2017-12-28 08:55:13 +00:00
|
|
|
char * shmFile;
|
2017-12-02 05:39:44 +00:00
|
|
|
bool showFPS;
|
2017-11-25 06:51:34 +00:00
|
|
|
bool useSpice;
|
2017-12-28 08:55:13 +00:00
|
|
|
char * spiceHost;
|
2017-11-25 07:20:30 +00:00
|
|
|
unsigned int spicePort;
|
2017-12-01 01:45:23 +00:00
|
|
|
bool scaleMouseInput;
|
2017-12-03 08:53:30 +00:00
|
|
|
bool hideMouse;
|
2017-12-17 09:09:47 +00:00
|
|
|
bool ignoreQuit;
|
2017-12-17 09:32:44 +00:00
|
|
|
|
2017-12-17 12:15:18 +00:00
|
|
|
bool forceRenderer;
|
|
|
|
unsigned int forceRendererIndex;
|
2017-12-17 11:21:59 +00:00
|
|
|
RendererOpts rendererOpts[LG_RENDERER_COUNT];
|
2017-11-25 06:51:34 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
struct AppState state;
|
|
|
|
struct AppParams params =
|
|
|
|
{
|
2017-12-28 08:55:13 +00:00
|
|
|
.configFile = "/etc/looking-glass.conf",
|
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,
|
2017-12-15 01:36:46 +00:00
|
|
|
.fullscreen = false,
|
2017-11-25 09:16:26 +00:00
|
|
|
.center = true,
|
|
|
|
.x = 0,
|
|
|
|
.y = 0,
|
2017-11-25 09:39:41 +00:00
|
|
|
.w = 1024,
|
|
|
|
.h = 768,
|
2017-12-28 04:30:03 +00:00
|
|
|
.shmFile = "/dev/shm/looking-glass",
|
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,
|
2017-12-17 09:09:47 +00:00
|
|
|
.hideMouse = true,
|
2017-12-17 12:15:18 +00:00
|
|
|
.ignoreQuit = false,
|
|
|
|
.forceRenderer = false
|
2017-11-25 06:51:34 +00:00
|
|
|
};
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2017-12-19 13:53:45 +00:00
|
|
|
static inline void updatePositionInfo()
|
2017-12-01 02:42:58 +00:00
|
|
|
{
|
2017-12-20 19:38:34 +00:00
|
|
|
if (!state.started)
|
|
|
|
return;
|
|
|
|
|
2017-12-01 02:42:58 +00:00
|
|
|
int w, h;
|
2017-12-10 14:31:52 +00:00
|
|
|
SDL_GetWindowSize(state.window, &w, &h);
|
2017-12-01 02:42:58 +00:00
|
|
|
|
|
|
|
if (params.keepAspect)
|
|
|
|
{
|
2017-12-08 06:02:33 +00:00
|
|
|
const float srcAspect = (float)state.srcSize.y / (float)state.srcSize.x;
|
2017-12-01 02:42:58 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-12-20 19:38:56 +00:00
|
|
|
state.scaleX = (float)state.srcSize.y / (float)state.dstRect.h;
|
|
|
|
state.scaleY = (float)state.srcSize.x / (float)state.dstRect.w;
|
|
|
|
|
|
|
|
DEBUG_INFO("client %dx%d, guest %dx%d, target %dx%d, scaleX: %.2f, scaleY %.2f",
|
|
|
|
w, h,
|
|
|
|
state.srcSize.x, state.srcSize.y,
|
|
|
|
state.dstRect.w, state.dstRect.h,
|
|
|
|
state.scaleX , state.scaleY
|
|
|
|
);
|
|
|
|
|
|
|
|
if (w != state.srcSize.x || h != state.srcSize.y)
|
|
|
|
DEBUG_WARN("Window size doesn't match guest resolution, cursor alignment may not be reliable");
|
2017-12-10 06:27:02 +00:00
|
|
|
|
2017-12-10 14:31:52 +00:00
|
|
|
if (state.lgr)
|
2017-12-10 16:47:07 +00:00
|
|
|
state.lgr->on_resize(state.lgrData, w, h, state.dstRect);
|
2017-12-06 15:37:46 +00:00
|
|
|
}
|
|
|
|
|
2017-12-19 13:53:45 +00:00
|
|
|
void mainThread()
|
2017-10-19 04:15:49 +00:00
|
|
|
{
|
2017-12-19 13:53:45 +00:00
|
|
|
while(state.running)
|
|
|
|
{
|
|
|
|
if (state.started)
|
2017-12-20 14:15:16 +00:00
|
|
|
{
|
2017-12-19 13:53:45 +00:00
|
|
|
if (!state.lgr->render(state.lgrData, state.window))
|
|
|
|
break;
|
2017-12-20 14:15:16 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
usleep(1000);
|
2017-12-19 13:53:45 +00:00
|
|
|
}
|
|
|
|
}
|
2017-12-15 08:13:36 +00:00
|
|
|
|
2017-12-19 13:53:45 +00:00
|
|
|
int cursorThread(void * unused)
|
|
|
|
{
|
|
|
|
struct KVMFRHeader header;
|
2017-12-15 08:13:36 +00:00
|
|
|
LG_RendererCursor cursorType = LG_CURSOR_COLOR;
|
2017-12-19 13:53:45 +00:00
|
|
|
uint32_t version = 0;
|
2017-12-06 17:51:54 +00:00
|
|
|
|
2017-12-15 01:02:08 +00:00
|
|
|
memset(&header, 0, sizeof(struct KVMFRHeader));
|
|
|
|
|
2017-12-06 15:37:46 +00:00
|
|
|
while(state.running)
|
|
|
|
{
|
2017-12-20 14:56:59 +00:00
|
|
|
// poll until we have cursor data
|
|
|
|
if(!(state.shm->flags & KVMFR_HEADER_FLAG_CURSOR))
|
|
|
|
{
|
|
|
|
nsleep(100);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
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
|
2017-12-19 13:53:45 +00:00
|
|
|
memcpy(&header, (KVMFRHeader *)state.shm, sizeof(struct KVMFRHeader));
|
|
|
|
__sync_and_and_fetch(&state.shm->flags, ~KVMFR_HEADER_FLAG_CURSOR);
|
2017-11-26 02:32:54 +00:00
|
|
|
|
2017-12-19 13:53:45 +00:00
|
|
|
if (header.detail.cursor.flags & KVMFR_CURSOR_FLAG_SHAPE &&
|
|
|
|
header.detail.cursor.version != version)
|
2017-12-14 23:51:40 +00:00
|
|
|
{
|
2017-12-19 13:53:45 +00:00
|
|
|
version = header.detail.cursor.version;
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2017-12-19 13:53:45 +00:00
|
|
|
bool bad = false;
|
|
|
|
switch(header.detail.cursor.type)
|
2017-12-11 21:08:14 +00:00
|
|
|
{
|
2017-12-19 13:53:45 +00:00
|
|
|
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;
|
2017-12-11 21:08:14 +00:00
|
|
|
default:
|
2017-12-19 13:53:45 +00:00
|
|
|
DEBUG_ERROR("Invalid cursor type");
|
|
|
|
bad = true;
|
2017-12-11 21:08:14 +00:00
|
|
|
break;
|
|
|
|
}
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2017-12-19 13:53:45 +00:00
|
|
|
if (bad)
|
2017-12-11 21:08:14 +00:00
|
|
|
break;
|
|
|
|
|
2017-12-19 13:53:45 +00:00
|
|
|
// check the data position is sane
|
|
|
|
const uint64_t dataSize = header.detail.frame.height * header.detail.frame.pitch;
|
|
|
|
if (header.detail.cursor.dataPos + dataSize > state.shmSize)
|
2017-12-05 09:33:05 +00:00
|
|
|
{
|
2017-12-19 13:53:45 +00:00
|
|
|
DEBUG_ERROR("The guest sent an invalid mouse dataPos");
|
2017-12-11 21:08:14 +00:00
|
|
|
break;
|
2017-12-05 09:33:05 +00:00
|
|
|
}
|
2017-11-25 09:21:57 +00:00
|
|
|
|
2017-12-19 13:53:45 +00:00
|
|
|
const uint8_t * data = (const uint8_t *)state.shm + header.detail.cursor.dataPos;
|
|
|
|
if (!state.lgr->on_mouse_shape(
|
|
|
|
state.lgrData,
|
|
|
|
cursorType,
|
|
|
|
header.detail.cursor.width,
|
|
|
|
header.detail.cursor.height,
|
|
|
|
header.detail.cursor.pitch,
|
|
|
|
data)
|
|
|
|
)
|
2017-12-05 09:33:05 +00:00
|
|
|
{
|
2017-12-19 13:53:45 +00:00
|
|
|
DEBUG_ERROR("Failed to update mouse shape");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2017-12-11 21:08:14 +00:00
|
|
|
|
2017-12-19 13:53:45 +00:00
|
|
|
if (header.detail.cursor.flags & KVMFR_CURSOR_FLAG_POS)
|
|
|
|
{
|
|
|
|
state.cursor.x = header.detail.cursor.x;
|
|
|
|
state.cursor.y = header.detail.cursor.y;
|
|
|
|
state.cursorVisible = header.detail.cursor.flags & KVMFR_CURSOR_FLAG_VISIBLE;
|
|
|
|
state.haveCursorPos = true;
|
|
|
|
|
|
|
|
state.lgr->on_mouse_event
|
|
|
|
(
|
|
|
|
state.lgrData,
|
|
|
|
state.cursorVisible,
|
|
|
|
state.cursor.x,
|
|
|
|
state.cursor.y
|
|
|
|
);
|
|
|
|
}
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2017-12-19 13:53:45 +00:00
|
|
|
// poll until we have cursor data
|
|
|
|
while(((state.shm->flags & KVMFR_HEADER_FLAG_CURSOR) == 0) && state.running)
|
|
|
|
{
|
|
|
|
usleep(1000);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
2017-12-15 07:47:44 +00:00
|
|
|
|
2017-12-19 13:53:45 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2017-12-15 08:13:36 +00:00
|
|
|
|
2017-12-19 13:53:45 +00:00
|
|
|
int frameThread(void * unused)
|
|
|
|
{
|
|
|
|
bool error = false;
|
|
|
|
struct KVMFRHeader header;
|
2017-12-15 08:13:36 +00:00
|
|
|
|
2017-12-19 13:53:45 +00:00
|
|
|
memset(&header, 0, sizeof(struct KVMFRHeader));
|
2017-12-15 08:13:36 +00:00
|
|
|
|
2017-12-19 13:53:45 +00:00
|
|
|
while(state.running)
|
|
|
|
{
|
|
|
|
// poll until we have a new frame
|
|
|
|
if(!(state.shm->flags & KVMFR_HEADER_FLAG_FRAME))
|
|
|
|
{
|
|
|
|
nsleep(100);
|
|
|
|
continue;
|
|
|
|
}
|
2017-10-19 04:15:49 +00:00
|
|
|
|
2017-12-19 13:53:45 +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, (KVMFRHeader *)state.shm, sizeof(struct KVMFRHeader));
|
|
|
|
__sync_and_and_fetch(&state.shm->flags, ~KVMFR_HEADER_FLAG_FRAME);
|
|
|
|
|
|
|
|
// sainty check of the frame format
|
|
|
|
if (
|
|
|
|
header.detail.frame.type >= FRAME_TYPE_MAX ||
|
|
|
|
header.detail.frame.width == 0 ||
|
|
|
|
header.detail.frame.height == 0 ||
|
|
|
|
header.detail.frame.pitch == 0 ||
|
|
|
|
header.detail.frame.dataPos == 0 ||
|
|
|
|
header.detail.frame.dataPos > state.shmSize ||
|
|
|
|
header.detail.frame.pitch < header.detail.frame.width
|
|
|
|
){
|
|
|
|
usleep(1000);
|
|
|
|
continue;
|
2017-11-25 09:21:57 +00:00
|
|
|
}
|
2017-12-12 15:22:47 +00:00
|
|
|
|
2017-12-19 13:53:45 +00:00
|
|
|
// setup the renderer format with the frame format details
|
|
|
|
LG_RendererFormat lgrFormat;
|
|
|
|
lgrFormat.width = header.detail.frame.width;
|
|
|
|
lgrFormat.height = header.detail.frame.height;
|
|
|
|
lgrFormat.stride = header.detail.frame.stride;
|
|
|
|
lgrFormat.pitch = header.detail.frame.pitch;
|
|
|
|
|
2017-12-29 11:48:21 +00:00
|
|
|
size_t dataSize;
|
2017-12-19 13:53:45 +00:00
|
|
|
switch(header.detail.frame.type)
|
2017-12-12 15:22:47 +00:00
|
|
|
{
|
2017-12-19 13:53:45 +00:00
|
|
|
case FRAME_TYPE_ARGB:
|
2017-12-29 11:48:21 +00:00
|
|
|
dataSize = lgrFormat.height * lgrFormat.pitch;
|
2017-12-29 10:20:51 +00:00
|
|
|
lgrFormat.comp = LG_COMPRESSION_NONE;
|
|
|
|
lgrFormat.bpp = 32;
|
2017-12-19 13:53:45 +00:00
|
|
|
break;
|
2017-12-12 15:22:47 +00:00
|
|
|
|
2017-12-29 10:20:51 +00:00
|
|
|
case FRAME_TYPE_H264:
|
2017-12-29 11:48:21 +00:00
|
|
|
dataSize = lgrFormat.pitch;
|
2017-12-29 10:20:51 +00:00
|
|
|
lgrFormat.comp = LG_COMPRESSION_H264;
|
|
|
|
lgrFormat.bpp = 0;
|
2017-12-19 13:53:45 +00:00
|
|
|
break;
|
2017-12-15 08:13:36 +00:00
|
|
|
|
2017-12-19 13:53:45 +00:00
|
|
|
default:
|
|
|
|
DEBUG_ERROR("Unsupported frameType");
|
|
|
|
error = true;
|
|
|
|
break;
|
|
|
|
}
|
2017-12-12 16:08:13 +00:00
|
|
|
|
2017-12-19 13:53:45 +00:00
|
|
|
if (error)
|
|
|
|
break;
|
2017-12-14 23:51:40 +00:00
|
|
|
|
2017-12-19 13:53:45 +00:00
|
|
|
// check the header's dataPos is sane
|
|
|
|
if (header.detail.frame.dataPos + dataSize > state.shmSize)
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("The guest sent an invalid dataPos");
|
|
|
|
break;
|
|
|
|
}
|
2017-12-15 08:13:36 +00:00
|
|
|
|
2017-12-19 13:53:45 +00:00
|
|
|
if (header.detail.frame.width != state.srcSize.x || header.detail.frame.height != state.srcSize.y)
|
|
|
|
{
|
|
|
|
state.srcSize.x = header.detail.frame.width;
|
|
|
|
state.srcSize.y = header.detail.frame.height;
|
|
|
|
if (params.autoResize)
|
|
|
|
SDL_SetWindowSize(state.window, header.detail.frame.width, header.detail.frame.height);
|
|
|
|
updatePositionInfo();
|
|
|
|
}
|
2017-12-12 15:22:47 +00:00
|
|
|
|
2017-12-19 13:53:45 +00:00
|
|
|
const uint8_t * data = (const uint8_t *)state.shm + header.detail.frame.dataPos;
|
|
|
|
if (!state.lgr->on_frame_event(state.lgrData, lgrFormat, data))
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("renderer on frame event returned failure");
|
|
|
|
break;
|
2017-12-12 15:22:47 +00:00
|
|
|
}
|
|
|
|
|
2017-12-19 13:53:45 +00:00
|
|
|
if (!state.started)
|
|
|
|
{
|
|
|
|
state.started = true;
|
2017-12-20 19:38:34 +00:00
|
|
|
updatePositionInfo();
|
2017-12-19 13:53:45 +00:00
|
|
|
}
|
2017-10-19 04:15:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
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-12-14 09:09:48 +00:00
|
|
|
switch(event.type)
|
2017-11-25 06:51:34 +00:00
|
|
|
{
|
2017-12-14 09:09:48 +00:00
|
|
|
case SDL_QUIT:
|
2017-12-17 09:09:47 +00:00
|
|
|
if (!params.ignoreQuit)
|
|
|
|
state.running = false;
|
2017-11-25 06:51:34 +00:00
|
|
|
break;
|
2017-12-14 09:09:48 +00:00
|
|
|
|
|
|
|
case SDL_WINDOWEVENT:
|
|
|
|
{
|
|
|
|
switch(event.window.event)
|
|
|
|
{
|
|
|
|
case SDL_WINDOWEVENT_ENTER:
|
|
|
|
realignGuest = true;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
|
|
|
updatePositionInfo();
|
|
|
|
realignGuest = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2017-11-25 06:51:34 +00:00
|
|
|
}
|
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-13 11:05:47 +00:00
|
|
|
if (realignGuest && state.haveCursorPos)
|
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
|
|
|
}
|
2017-12-12 15:22:47 +00:00
|
|
|
x -= state.cursor.x;
|
|
|
|
y -= state.cursor.y;
|
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;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
2017-10-19 04:15:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-12-17 09:09:47 +00:00
|
|
|
void intHandler(int signal)
|
|
|
|
{
|
|
|
|
switch(signal)
|
|
|
|
{
|
|
|
|
case SIGINT:
|
|
|
|
state.running = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-28 04:30:03 +00:00
|
|
|
static void * map_memory()
|
|
|
|
{
|
|
|
|
struct stat st;
|
|
|
|
if (stat(params.shmFile, &st) < 0)
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("Failed to stat the shared memory file: %s", params.shmFile);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
state.shmSize = st.st_size;
|
|
|
|
state.shmFD = open(params.shmFile, O_RDWR, (mode_t)0600);
|
|
|
|
if (state.shmFD < 0)
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("Failed to open the shared memory file: %s", params.shmFile);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void * map = mmap(0, st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, state.shmFD, 0);
|
|
|
|
if (map == MAP_FAILED)
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("Failed to map the shared memory file: %s", params.shmFile);
|
|
|
|
close(state.shmFD);
|
|
|
|
state.shmFD = 0;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return map;
|
|
|
|
}
|
|
|
|
|
2017-12-17 12:15:18 +00:00
|
|
|
static bool try_renderer(const int index, const LG_RendererParams lgrParams, Uint32 * sdlFlags)
|
|
|
|
{
|
|
|
|
const LG_Renderer *r = LG_Renderers[index];
|
|
|
|
RendererOpts *opts = ¶ms.rendererOpts[index];
|
|
|
|
|
|
|
|
if (!IS_LG_RENDERER_VALID(r))
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("FIXME: Renderer %d is invalid, skipping", index);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// create the renderer
|
|
|
|
state.lgrData = NULL;
|
|
|
|
if (!r->create(&state.lgrData, lgrParams))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// set it's options
|
|
|
|
for(unsigned int i = 0; i < opts->argc; ++i)
|
|
|
|
opts->argv[i].opt->handler(state.lgrData, opts->argv[i].value);
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2017-11-25 07:20:30 +00:00
|
|
|
int run()
|
2017-10-19 04:15:49 +00:00
|
|
|
{
|
2017-12-10 22:23:58 +00:00
|
|
|
DEBUG_INFO("Looking Glass (" BUILD_VERSION ")");
|
2017-12-20 14:35:36 +00:00
|
|
|
DEBUG_INFO("Locking Method: " LG_LOCK_MODE);
|
2017-12-10 22:23:58 +00:00
|
|
|
|
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-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
|
|
|
|
signal(SIGINT, intHandler);
|
|
|
|
|
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-10 06:04:02 +00:00
|
|
|
FcConfig * config = FcInitLoadConfigAndFonts();
|
|
|
|
if (!config)
|
2017-12-02 06:46:09 +00:00
|
|
|
{
|
2017-12-10 06:04:02 +00:00
|
|
|
DEBUG_ERROR("FcInitLoadConfigAndFonts Failed");
|
2017-12-02 06:46:09 +00:00
|
|
|
return -1;
|
|
|
|
}
|
2017-12-10 06:04:02 +00:00
|
|
|
|
|
|
|
FcPattern * pat = FcNameParse((const FcChar8*)"FreeMono");
|
|
|
|
FcConfigSubstitute (config, pat, FcMatchPattern);
|
|
|
|
FcDefaultSubstitute(pat);
|
|
|
|
FcResult result;
|
|
|
|
FcChar8 * file = NULL;
|
|
|
|
FcPattern * font = FcFontMatch(config, pat, &result);
|
|
|
|
|
|
|
|
if (font && (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch))
|
|
|
|
{
|
|
|
|
state.font = TTF_OpenFont((char *)file, 14);
|
|
|
|
if (!state.font)
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("TTL_OpenFont Failed");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("Failed to locate a font for FPS display");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
FcPatternDestroy(pat);
|
2017-12-02 05:39:44 +00:00
|
|
|
}
|
|
|
|
|
2017-12-14 06:42:16 +00:00
|
|
|
LG_RendererParams lgrParams;
|
|
|
|
lgrParams.font = state.font;
|
|
|
|
lgrParams.showFPS = params.showFPS;
|
|
|
|
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];
|
|
|
|
DEBUG_INFO("Using: %s", state.lgr->get_name());
|
|
|
|
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
|
|
|
|
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,
|
|
|
|
(
|
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) |
|
|
|
|
sdlFlags
|
2017-11-25 09:16:26 +00:00
|
|
|
)
|
2017-11-25 06:51:34 +00:00
|
|
|
);
|
|
|
|
|
2017-12-15 02:27:53 +00:00
|
|
|
if (params.fullscreen)
|
|
|
|
{
|
|
|
|
SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
|
|
|
|
}
|
|
|
|
|
2017-12-10 17:11:36 +00:00
|
|
|
// set the compositor hint to bypass for low latency
|
|
|
|
SDL_SysWMinfo wminfo;
|
|
|
|
SDL_VERSION(&wminfo.version);
|
|
|
|
if (SDL_GetWindowWMInfo(state.window, &wminfo))
|
|
|
|
{
|
|
|
|
if (wminfo.subsystem == SDL_SYSWM_X11)
|
|
|
|
{
|
|
|
|
Atom NETWM_BYPASS_COMPOSITOR = XInternAtom(
|
|
|
|
wminfo.info.x11.display,
|
|
|
|
"NETWM_BYPASS_COMPOSITOR",
|
|
|
|
False);
|
|
|
|
|
|
|
|
unsigned long value = 1;
|
|
|
|
XChangeProperty(
|
|
|
|
wminfo.info.x11.display,
|
|
|
|
wminfo.info.x11.window,
|
|
|
|
NETWM_BYPASS_COMPOSITOR,
|
|
|
|
XA_CARDINAL,
|
|
|
|
32,
|
|
|
|
PropModeReplace,
|
|
|
|
(unsigned char *)&value,
|
|
|
|
1
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-19 07:50:42 +00:00
|
|
|
SDL_Thread *t_spice = NULL;
|
|
|
|
SDL_Thread *t_event = NULL;
|
2017-10-19 04:15:49 +00:00
|
|
|
|
|
|
|
while(1)
|
|
|
|
{
|
2017-12-28 04:30:03 +00:00
|
|
|
state.shm = (struct KVMFRHeader *)map_memory();
|
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 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)))
|
|
|
|
{
|
2017-12-19 13:53:45 +00:00
|
|
|
DEBUG_ERROR("event create thread failed");
|
2017-10-19 04:15:49 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2017-12-13 23:08:47 +00:00
|
|
|
// flag the host that we are starting up this is important so that
|
|
|
|
// the host wakes up if it is waiting on an interrupt, the host will
|
|
|
|
// also send us the current mouse shape since we won't know it yet
|
|
|
|
DEBUG_INFO("Waiting for host to signal it's ready...");
|
|
|
|
__sync_or_and_fetch(&state.shm->flags, KVMFR_HEADER_FLAG_RESTART);
|
|
|
|
while(state.running && (state.shm->flags & KVMFR_HEADER_FLAG_RESTART))
|
|
|
|
usleep(1000);
|
|
|
|
DEBUG_INFO("Host ready, starting session");
|
|
|
|
|
2017-12-19 13:53:45 +00:00
|
|
|
// check the header's magic and version are valid
|
|
|
|
if (memcmp(state.shm->magic, KVMFR_HEADER_MAGIC, sizeof(KVMFR_HEADER_MAGIC)) != 0)
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("Invalid header magic, is the host running?");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (state.shm->version != KVMFR_HEADER_VERSION)
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("KVMFR version missmatch, expected %u but got %u", KVMFR_HEADER_VERSION, state.shm->version);
|
|
|
|
DEBUG_ERROR("This is not a bug, ensure you have the right version of looking-glass-host.exe on the guest");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(t_event = SDL_CreateThread(cursorThread, "cursorThread", NULL)))
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("cursor create thread failed");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(t_event = SDL_CreateThread(frameThread, "frameThread", NULL)))
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("frame create thread failed");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
mainThread();
|
2017-10-19 04:15:49 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
state.running = false;
|
|
|
|
|
|
|
|
if (t_event)
|
|
|
|
SDL_WaitThread(t_event, NULL);
|
|
|
|
|
|
|
|
if (t_spice)
|
|
|
|
SDL_WaitThread(t_spice, NULL);
|
|
|
|
|
2017-12-14 06:42:16 +00:00
|
|
|
if (state.lgr)
|
|
|
|
state.lgr->deinitialize(state.lgrData);
|
|
|
|
|
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);
|
|
|
|
|
2017-12-28 04:30:03 +00:00
|
|
|
if (state.shm)
|
|
|
|
{
|
|
|
|
munmap(state.shm, state.shmSize);
|
|
|
|
close(state.shmFD);
|
|
|
|
}
|
2017-10-19 04:15:49 +00:00
|
|
|
|
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-12-28 08:55:13 +00:00
|
|
|
" -C PATH Specify an additional configuration file to load\n"
|
2017-12-28 04:30:03 +00:00
|
|
|
" -f PATH Specify the path to the shared memory file [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-12-02 05:39:44 +00:00
|
|
|
" -k Enable FPS display\n"
|
2017-12-17 12:15:18 +00:00
|
|
|
" -g NAME Force the use of a specific renderer\n"
|
2017-12-19 04:40:52 +00:00
|
|
|
" -o OPTION Specify a renderer option (ie: opengl:vsync=0)\n"
|
2017-12-17 11:45:26 +00:00
|
|
|
" Alternatively specify \"list\" to list all renderers and their options\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"
|
2017-12-15 02:46:19 +00:00
|
|
|
" -F Borderless fullscreen mode\n"
|
2017-11-25 09:16:26 +00:00
|
|
|
" -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"
|
2017-12-17 09:09:47 +00:00
|
|
|
" -Q Ignore requests to quit (ie: Alt+F4)\n"
|
2017-11-25 09:16:26 +00:00
|
|
|
"\n"
|
|
|
|
" -l License information\n"
|
2017-11-25 07:20:30 +00:00
|
|
|
"\n",
|
|
|
|
app,
|
|
|
|
app,
|
2017-12-28 04:30:03 +00:00
|
|
|
params.shmFile,
|
2017-11-25 07:20:30 +00:00
|
|
|
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"
|
2017-12-11 17:30:47 +00:00
|
|
|
"https://looking-glass.hostfission.com\n"
|
2017-11-25 07:20:30 +00:00
|
|
|
"\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"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2017-12-28 08:55:13 +00:00
|
|
|
static bool load_config(const char * configFile)
|
|
|
|
{
|
|
|
|
config_t cfg;
|
|
|
|
int itmp;
|
|
|
|
const char *stmp;
|
|
|
|
|
|
|
|
config_init(&cfg);
|
|
|
|
if (!config_read_file(&cfg, configFile))
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("Config file error %s:%d - %s",
|
|
|
|
config_error_file(&cfg),
|
|
|
|
config_error_line(&cfg),
|
|
|
|
config_error_text(&cfg)
|
|
|
|
);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
config_setting_t * global = config_lookup(&cfg, "global");
|
|
|
|
if (global)
|
|
|
|
{
|
|
|
|
if (config_setting_lookup_string(global, "shmFile", &stmp))
|
|
|
|
{
|
|
|
|
free(params.shmFile);
|
|
|
|
params.shmFile = strdup(stmp);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (config_setting_lookup_string(global, "forceRenderer", &stmp))
|
|
|
|
{
|
|
|
|
bool ok = false;
|
|
|
|
for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i)
|
|
|
|
if (strcasecmp(LG_Renderers[i]->get_name(), stmp) == 0)
|
|
|
|
{
|
|
|
|
params.forceRenderer = true;
|
|
|
|
params.forceRendererIndex = i;
|
|
|
|
ok = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ok)
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("No such renderer: %s", stmp);
|
|
|
|
config_destroy(&cfg);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (config_setting_lookup_bool (global, "scaleMouseInput", &itmp)) params.scaleMouseInput = (itmp != 0);
|
|
|
|
if (config_setting_lookup_bool (global, "hideMouse" , &itmp)) params.hideMouse = (itmp != 0);
|
|
|
|
if (config_setting_lookup_bool (global, "showFPS" , &itmp)) params.showFPS = (itmp != 0);
|
|
|
|
if (config_setting_lookup_bool (global, "autoResize" , &itmp)) params.autoResize = (itmp != 0);
|
|
|
|
if (config_setting_lookup_bool (global, "allowResize" , &itmp)) params.allowResize = (itmp != 0);
|
|
|
|
if (config_setting_lookup_bool (global, "keepAspect" , &itmp)) params.keepAspect = (itmp != 0);
|
|
|
|
if (config_setting_lookup_bool (global, "borderless" , &itmp)) params.borderless = (itmp != 0);
|
|
|
|
if (config_setting_lookup_bool (global, "fullScreen" , &itmp)) params.fullscreen = (itmp != 0);
|
|
|
|
if (config_setting_lookup_bool (global, "ignoreQuit" , &itmp)) params.ignoreQuit = (itmp != 0);
|
|
|
|
|
|
|
|
if (config_setting_lookup_int(global, "x", ¶ms.x)) params.center = false;
|
|
|
|
if (config_setting_lookup_int(global, "y", ¶ms.y)) params.center = false;
|
|
|
|
|
|
|
|
if (config_setting_lookup_int(global, "w", &itmp))
|
|
|
|
{
|
|
|
|
if (itmp < 1)
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("Invalid window width, must be greater then 1px");
|
|
|
|
config_destroy(&cfg);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
params.w = (unsigned int)itmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (config_setting_lookup_int(global, "h", &itmp))
|
|
|
|
{
|
|
|
|
if (itmp < 1)
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("Invalid window height, must be greater then 1px");
|
|
|
|
config_destroy(&cfg);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
params.h = (unsigned int)itmp;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i)
|
|
|
|
{
|
|
|
|
const LG_Renderer * r = LG_Renderers[i];
|
|
|
|
RendererOpts * opts = ¶ms.rendererOpts[i];
|
|
|
|
config_setting_t * group = config_lookup(&cfg, r->get_name());
|
|
|
|
if (!group)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
for(unsigned int j = 0; j < r->option_count; ++j)
|
|
|
|
{
|
|
|
|
const char * name = r->options[j].name;
|
|
|
|
if (!config_setting_lookup_string(group, name, &stmp))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (r->options[j].validator && !r->options[j].validator(stmp))
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("Renderer \"%s\" reported invalid value for option \"%s\"", r->get_name(), name);
|
|
|
|
config_destroy(&cfg);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (opts->argc == opts->size)
|
|
|
|
{
|
|
|
|
opts->size += 5;
|
|
|
|
opts->argv = realloc(opts->argv, sizeof(LG_RendererOptValue) * opts->size);
|
|
|
|
}
|
|
|
|
|
|
|
|
opts->argv[opts->argc].opt = &r->options[j];
|
|
|
|
opts->argv[opts->argc].value = strdup(stmp);
|
|
|
|
++opts->argc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
config_destroy(&cfg);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-11-25 07:20:30 +00:00
|
|
|
int main(int argc, char * argv[])
|
|
|
|
{
|
2017-12-28 08:55:13 +00:00
|
|
|
params.shmFile = strdup(params.shmFile );
|
|
|
|
params.spiceHost = strdup(params.spiceHost);
|
|
|
|
|
|
|
|
{
|
|
|
|
// load any global then local config options first
|
|
|
|
struct stat st;
|
|
|
|
if (stat("/etc/looking-glass.conf", &st) >= 0)
|
|
|
|
{
|
|
|
|
DEBUG_INFO("Loading config from: /etc/looking-glass.conf");
|
|
|
|
if (!load_config("/etc/looking-glass.conf"))
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct passwd * pw = getpwuid(getuid());
|
|
|
|
const char pattern[] = "%s/.looking-glass.conf";
|
|
|
|
const size_t len = strlen(pw->pw_dir) + sizeof(pattern);
|
|
|
|
char buffer[len];
|
|
|
|
snprintf(buffer, len, pattern, pw->pw_dir);
|
|
|
|
if (stat(buffer, &st) >= 0)
|
|
|
|
{
|
|
|
|
DEBUG_INFO("Loading config from: %s", buffer);
|
|
|
|
if (!load_config(buffer))
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-19 00:58:38 +00:00
|
|
|
for(;;)
|
|
|
|
{
|
2017-12-28 08:55:13 +00:00
|
|
|
switch(getopt(argc, argv, "hC:f:sc:p:jMvkg:o:anrdFx:y:w:b:Ql"))
|
2017-11-25 07:20:30 +00:00
|
|
|
{
|
|
|
|
case '?':
|
|
|
|
case 'h':
|
|
|
|
default :
|
|
|
|
doHelp(argv[0]);
|
2017-12-19 00:58:38 +00:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
case -1:
|
|
|
|
break;
|
2017-11-25 07:20:30 +00:00
|
|
|
|
2017-12-28 08:55:13 +00:00
|
|
|
case 'C':
|
|
|
|
params.configFile = optarg;
|
|
|
|
if (!load_config(optarg))
|
|
|
|
return -1;
|
|
|
|
continue;
|
|
|
|
|
2017-11-25 07:20:30 +00:00
|
|
|
case 'f':
|
2017-12-28 08:55:13 +00:00
|
|
|
free(params.shmFile);
|
|
|
|
params.shmFile = strdup(optarg);
|
2017-12-19 00:58:38 +00:00
|
|
|
continue;
|
2017-11-25 07:20:30 +00:00
|
|
|
|
|
|
|
case 's':
|
|
|
|
params.useSpice = false;
|
2017-12-19 00:58:38 +00:00
|
|
|
continue;
|
2017-11-25 07:20:30 +00:00
|
|
|
|
|
|
|
case 'c':
|
2017-12-28 08:55:13 +00:00
|
|
|
free(params.spiceHost);
|
|
|
|
params.spiceHost = strdup(optarg);
|
2017-12-19 00:58:38 +00:00
|
|
|
continue;
|
2017-11-25 07:20:30 +00:00
|
|
|
|
|
|
|
case 'p':
|
|
|
|
params.spicePort = atoi(optarg);
|
2017-12-19 00:58:38 +00:00
|
|
|
continue;
|
2017-11-25 07:20:30 +00:00
|
|
|
|
2017-12-01 01:45:23 +00:00
|
|
|
case 'j':
|
|
|
|
params.scaleMouseInput = false;
|
2017-12-19 00:58:38 +00:00
|
|
|
continue;
|
2017-12-01 01:45:23 +00:00
|
|
|
|
2017-12-03 08:53:30 +00:00
|
|
|
case 'M':
|
|
|
|
params.hideMouse = false;
|
2017-12-19 00:58:38 +00:00
|
|
|
continue;
|
2017-12-03 08:53:30 +00:00
|
|
|
|
2017-12-02 05:39:44 +00:00
|
|
|
case 'k':
|
|
|
|
params.showFPS = true;
|
2017-12-19 00:58:38 +00:00
|
|
|
continue;
|
2017-12-02 05:39:44 +00:00
|
|
|
|
2017-12-17 12:15:18 +00:00
|
|
|
case 'g':
|
|
|
|
{
|
|
|
|
bool ok = false;
|
|
|
|
for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i)
|
|
|
|
if (strcasecmp(LG_Renderers[i]->get_name(), optarg) == 0)
|
|
|
|
{
|
|
|
|
params.forceRenderer = true;
|
|
|
|
params.forceRendererIndex = i;
|
|
|
|
ok = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ok)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "No such renderer: %s\n", optarg);
|
|
|
|
fprintf(stderr, "Use '-o list' obtain a list of options\n");
|
|
|
|
doHelp(argv[0]);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2017-12-19 00:58:38 +00:00
|
|
|
continue;
|
2017-12-17 12:15:18 +00:00
|
|
|
}
|
|
|
|
|
2017-12-17 09:32:44 +00:00
|
|
|
case 'o':
|
2017-12-17 11:21:59 +00:00
|
|
|
{
|
2017-12-17 11:33:02 +00:00
|
|
|
if (strcasecmp(optarg, "list") == 0)
|
|
|
|
{
|
2017-12-18 23:56:23 +00:00
|
|
|
size_t maxLen = 0;
|
|
|
|
for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i)
|
|
|
|
{
|
|
|
|
const LG_Renderer * r = LG_Renderers[i];
|
|
|
|
for(unsigned int j = 0; j < r->option_count; ++j)
|
|
|
|
{
|
|
|
|
const size_t len = strlen(r->options[j].name);
|
|
|
|
if (len > maxLen)
|
|
|
|
maxLen = len;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-17 11:33:02 +00:00
|
|
|
fprintf(stderr, "\nRenderer Option List\n");
|
|
|
|
for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i)
|
|
|
|
{
|
|
|
|
const LG_Renderer * r = LG_Renderers[i];
|
|
|
|
fprintf(stderr, "\n%s\n", r->get_name());
|
2017-12-18 23:56:23 +00:00
|
|
|
for(unsigned int j = 0; j < r->option_count; ++j)
|
|
|
|
{
|
|
|
|
const size_t pad = maxLen - strlen(r->options[j].name);
|
|
|
|
for(int i = 0; i < pad; ++i)
|
|
|
|
fputc(' ', stderr);
|
|
|
|
|
|
|
|
fprintf(stderr, " %s - %s\n", r->options[j].name, r->options[j].desc);
|
|
|
|
}
|
2017-12-17 11:33:02 +00:00
|
|
|
}
|
|
|
|
fprintf(stderr, "\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2017-12-17 11:21:59 +00:00
|
|
|
const LG_Renderer * renderer = NULL;
|
|
|
|
RendererOpts * opts = NULL;
|
|
|
|
|
|
|
|
const size_t len = strlen(optarg);
|
|
|
|
const char * name = strtok(optarg, ":");
|
|
|
|
|
|
|
|
for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i)
|
|
|
|
if (strcasecmp(LG_Renderers[i]->get_name(), name) == 0)
|
|
|
|
{
|
|
|
|
renderer = LG_Renderers[i];
|
|
|
|
opts = ¶ms.rendererOpts[i];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!renderer)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "No such renderer: %s\n", name);
|
|
|
|
doHelp(argv[0]);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char * option = strtok(NULL , "=");
|
|
|
|
if (!option)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "Renderer option name not specified\n");
|
|
|
|
doHelp(argv[0]);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
const LG_RendererOpt * opt = NULL;
|
|
|
|
for(unsigned int i = 0; i < renderer->option_count; ++i)
|
|
|
|
if (strcasecmp(option, renderer->options[i].name) == 0)
|
|
|
|
{
|
|
|
|
opt = &renderer->options[i];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!opt)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "Renderer \"%s\" doesn't have the option: %s\n", renderer->get_name(), option);
|
|
|
|
doHelp(argv[0]);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char * value = NULL;
|
|
|
|
if (len > strlen(name) + strlen(option) + 2)
|
|
|
|
value = option + strlen(option) + 1;
|
|
|
|
|
|
|
|
if (opt->validator && !opt->validator(value))
|
|
|
|
{
|
2017-12-28 08:55:13 +00:00
|
|
|
fprintf(stderr, "Renderer \"%s\" reported invalid value for option \"%s\"\n", renderer->get_name(), option);
|
2017-12-17 11:21:59 +00:00
|
|
|
doHelp(argv[0]);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (opts->argc == opts->size)
|
|
|
|
{
|
|
|
|
opts->size += 5;
|
|
|
|
opts->argv = realloc(opts->argv, sizeof(LG_RendererOptValue) * opts->size);
|
|
|
|
}
|
|
|
|
|
|
|
|
opts->argv[opts->argc].opt = opt;
|
2017-12-28 08:55:13 +00:00
|
|
|
opts->argv[opts->argc].value = strdup(value);
|
2017-12-17 11:21:59 +00:00
|
|
|
++opts->argc;
|
2017-12-19 00:58:38 +00:00
|
|
|
continue;
|
2017-12-17 11:21:59 +00:00
|
|
|
}
|
2017-12-17 09:32:44 +00:00
|
|
|
|
2017-11-25 09:16:26 +00:00
|
|
|
case 'a':
|
|
|
|
params.autoResize = true;
|
2017-12-19 00:58:38 +00:00
|
|
|
continue;
|
2017-11-25 09:16:26 +00:00
|
|
|
|
2017-11-28 08:48:40 +00:00
|
|
|
case 'n':
|
|
|
|
params.allowResize = false;
|
2017-12-19 00:58:38 +00:00
|
|
|
continue;
|
2017-11-28 08:48:40 +00:00
|
|
|
|
2017-11-28 08:16:47 +00:00
|
|
|
case 'r':
|
|
|
|
params.keepAspect = false;
|
2017-12-19 00:58:38 +00:00
|
|
|
continue;
|
2017-11-28 08:16:47 +00:00
|
|
|
|
2017-11-25 09:16:26 +00:00
|
|
|
case 'd':
|
|
|
|
params.borderless = true;
|
2017-12-19 00:58:38 +00:00
|
|
|
continue;
|
2017-11-25 09:16:26 +00:00
|
|
|
|
2017-12-15 01:36:46 +00:00
|
|
|
case 'F':
|
|
|
|
params.fullscreen = true;
|
2017-12-19 00:58:38 +00:00
|
|
|
continue;
|
2017-12-15 01:36:46 +00:00
|
|
|
|
2017-11-25 09:16:26 +00:00
|
|
|
case 'x':
|
|
|
|
params.center = false;
|
|
|
|
params.x = atoi(optarg);
|
2017-12-19 00:58:38 +00:00
|
|
|
continue;
|
2017-11-25 09:16:26 +00:00
|
|
|
|
|
|
|
case 'y':
|
|
|
|
params.center = false;
|
|
|
|
params.y = atoi(optarg);
|
2017-12-19 00:58:38 +00:00
|
|
|
continue;
|
2017-11-25 09:16:26 +00:00
|
|
|
|
|
|
|
case 'w':
|
|
|
|
params.w = atoi(optarg);
|
2017-12-19 00:58:38 +00:00
|
|
|
continue;
|
2017-11-25 09:16:26 +00:00
|
|
|
|
|
|
|
case 'b':
|
|
|
|
params.h = atoi(optarg);
|
2017-12-19 00:58:38 +00:00
|
|
|
continue;
|
2017-11-25 09:16:26 +00:00
|
|
|
|
2017-12-17 09:09:47 +00:00
|
|
|
case 'Q':
|
|
|
|
params.ignoreQuit = true;
|
2017-12-19 00:58:38 +00:00
|
|
|
continue;
|
2017-12-17 09:09:47 +00:00
|
|
|
|
2017-11-25 07:20:30 +00:00
|
|
|
case 'l':
|
|
|
|
doLicense();
|
|
|
|
return 0;
|
|
|
|
}
|
2017-12-19 00:58:38 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (optind != argc)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "A non option was supplied\n");
|
|
|
|
doHelp(argv[0]);
|
|
|
|
return -1;
|
|
|
|
}
|
2017-11-25 07:20:30 +00:00
|
|
|
|
2017-12-28 08:55:13 +00:00
|
|
|
const int ret = run();
|
|
|
|
|
|
|
|
free(params.shmFile);
|
|
|
|
free(params.spiceHost);
|
|
|
|
for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i)
|
|
|
|
{
|
|
|
|
RendererOpts * opts = ¶ms.rendererOpts[i];
|
|
|
|
for(unsigned int j = 0; j < opts->argc; ++j)
|
|
|
|
free(opts->argv[j].value);
|
|
|
|
free(opts->argv);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return ret;
|
2017-10-19 04:15:49 +00:00
|
|
|
}
|