mirror of
https://github.com/gnif/LookingGlass.git
synced 2024-11-22 05:27:20 +00:00
Initial import of project to git
This commit is contained in:
commit
202985097e
42
client/KVMGFXHeader.h
Normal file
42
client/KVMGFXHeader.h
Normal file
@ -0,0 +1,42 @@
|
||||
|
||||
#define KVMGFX_HEADER_MAGIC "[[KVMGFXHeader]]"
|
||||
|
||||
typedef enum FrameType
|
||||
{
|
||||
FRAME_TYPE_INVALID ,
|
||||
FRAME_TYPE_ARGB , // ABGR interleaved: A,R,G,B 32bpp
|
||||
FRAME_TYPE_RGB , // RGB interleaved : R,G,B 24bpp
|
||||
FRAME_TYPE_YUV420P , // YUV420 12bpp
|
||||
FRAME_TYPE_ARGB10 , // rgb 10 bit packed, a2 b10 r10
|
||||
FRAME_TYPE_XOR , // xor of the previous frame: R, G, B
|
||||
FRAME_TYPE_MAX , // sentinel value
|
||||
} FrameType;
|
||||
|
||||
typedef enum FrameComp
|
||||
{
|
||||
FRAME_COMP_NONE , // no compression
|
||||
FRAME_COMP_BLACK_RLE , // basic run length encoding of black pixels for XOR mode
|
||||
FRAME_COMP_MAX , // sentinel valule
|
||||
} FrameComp;
|
||||
|
||||
struct KVMGFXHeader
|
||||
{
|
||||
char magic[sizeof(KVMGFX_HEADER_MAGIC)];
|
||||
uint32_t version; // version of this structure
|
||||
FrameType frameType; // the frame type
|
||||
FrameComp compType; // frame compression mode
|
||||
uint32_t width; // the width
|
||||
uint32_t height; // the height
|
||||
uint32_t stride; // the row stride
|
||||
uint64_t frames; // total frame count
|
||||
uint64_t clientFrame; // current client frame
|
||||
uint32_t dataLen; // total lengh of the data after this header
|
||||
};
|
||||
|
||||
#pragma pack(push,1)
|
||||
struct RLEHeader
|
||||
{
|
||||
uint8_t magic[3];
|
||||
uint16_t length;
|
||||
};
|
||||
#pragma pack(pop)
|
13
client/Makefile
Normal file
13
client/Makefile
Normal file
@ -0,0 +1,13 @@
|
||||
CFLAGS=-g -Og -std=gnu99 -march=native -Wall -Werror
|
||||
LDFLAGS=-lrt -lGL
|
||||
|
||||
CFLAGS+=`pkg-config --cflags sdl2`
|
||||
LDFLAGS+=`pkg-config --libs sdl2`
|
||||
|
||||
CFLAGS+=`pkg-config --cflags libssl openssl`
|
||||
LDFLAGS+=`pkg-config --libs libssl openssl`
|
||||
|
||||
CFLAGS+=`pkg-config --cflags spice-protocol`
|
||||
|
||||
all:
|
||||
gcc ${CFLAGS} -o main main.c spice.c ${LDFLAGS}
|
16
client/debug.h
Normal file
16
client/debug.h
Normal file
@ -0,0 +1,16 @@
|
||||
#ifdef DEBUG
|
||||
#define DEBUG_PRINT(type, fmt, args...) do {fprintf(stderr, type " %-30s : %-5u | " fmt "\n", __FUNCTION__, __LINE__, ##args);} while (0)
|
||||
#else
|
||||
#define DEBUG_PRINT(type, fmt, args...) do {} while(0)
|
||||
#endif
|
||||
|
||||
#define DEBUG_INFO(fmt, args...) DEBUG_PRINT("[I]", fmt, ##args)
|
||||
#define DEBUG_WARN(fmt, args...) DEBUG_PRINT("[W]", fmt, ##args)
|
||||
#define DEBUG_ERROR(fmt, args...) DEBUG_PRINT("[E]", fmt, ##args)
|
||||
#define DEBUG_FIXME(fmt, args...) DEBUG_PRINT("[F]", fmt, ##args)
|
||||
|
||||
#ifdef DEBUG_SPICE
|
||||
#define DEBUG_PROTO(fmt, args...) DEBUG_PRINT("[P]", fmt, ##args)
|
||||
#else
|
||||
#define DEBUG_PROTO(fmt, args...) do {} while(0)
|
||||
#endif
|
3
client/i3start.sh
Executable file
3
client/i3start.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
i3-msg 'workspace 10; move workspace to DP-0; exec /home/geoff/Projects/kvm-shm/main'
|
37
client/kb.h
Normal file
37
client/kb.h
Normal file
@ -0,0 +1,37 @@
|
||||
static uint32_t usb_to_ps2[] =
|
||||
{
|
||||
0x000000, 0x000000, 0x000000, 0x000000, 0x00001e, 0x000030, 0x00002e,
|
||||
0x000020, 0x000012, 0x000021, 0x000022, 0x000023, 0x000017, 0x000024,
|
||||
0x000025, 0x000026, 0x000032, 0x000031, 0x000018, 0x000019, 0x000010,
|
||||
0x000013, 0x00001f, 0x000014, 0x000016, 0x00002f, 0x000011, 0x00002d,
|
||||
0x000015, 0x00002c, 0x000002, 0x000003, 0x000004, 0x000005, 0x000006,
|
||||
0x000007, 0x000008, 0x000009, 0x00000a, 0x00000b, 0x00001c, 0x000001,
|
||||
0x00000e, 0x00000f, 0x000039, 0x00000c, 0x00000d, 0x00001a, 0x00001b,
|
||||
0x00002b, 0x00002b, 0x000027, 0x000028, 0x000029, 0x000033, 0x000034,
|
||||
0x000035, 0x00003a, 0x00003b, 0x00003c, 0x00003d, 0x00003e, 0x00003f,
|
||||
0x000040, 0x000041, 0x000042, 0x000043, 0x000044, 0x000057, 0x000058,
|
||||
0x00e037, 0x000046, 0x00e046, 0x00e052, 0x00e047, 0x00e049, 0x00e053,
|
||||
0x00e04f, 0x00e051, 0x00e04d, 0x00e04b, 0x00e050, 0x00e048, 0x000045,
|
||||
0x00e035, 0x000037, 0x00004a, 0x00004e, 0x00e01c, 0x00004f, 0x000050,
|
||||
0x000051, 0x00004b, 0x00004c, 0x00004d, 0x000047, 0x000048, 0x000049,
|
||||
0x000052, 0x000053, 0x000056, 0x00e05d, 0x000000, 0x000059, 0x00005d,
|
||||
0x00005e, 0x00005f, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x00007e, 0x000000, 0x000073, 0x000070, 0x00007d, 0x000079, 0x00007b,
|
||||
0x00005c, 0x000000, 0x000000, 0x000000, 0x0000f2, 0x0000f1, 0x000078,
|
||||
0x000077, 0x000076, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x00001d, 0x00002a, 0x000038, 0x00e05b, 0x00e01d, 0x000036, 0x00e038,
|
||||
0x00e05c
|
||||
};
|
BIN
client/main
Executable file
BIN
client/main
Executable file
Binary file not shown.
553
client/main.c
Normal file
553
client/main.c
Normal file
@ -0,0 +1,553 @@
|
||||
#include <SDL2/SDL.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <GL/gl.h>
|
||||
|
||||
#define DEBUG
|
||||
#include "debug.h"
|
||||
#include "KVMGFXHeader.h"
|
||||
#include "spice.h"
|
||||
#include "kb.h"
|
||||
|
||||
#define MAP_SIZE (16*1024*1024)
|
||||
#define COPY_THREADS 4
|
||||
|
||||
typedef void (*CompFunc)(uint8_t * dst, const uint8_t * src, const size_t len);
|
||||
typedef void (*DrawFunc)(CompFunc compFunc, SDL_Texture * texture, uint8_t * dst, const uint8_t * src);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SDL_mutex * mutex;
|
||||
SDL_cond * cond;
|
||||
SDL_Thread * thread;
|
||||
|
||||
bool rdy;
|
||||
void *dst;
|
||||
const void * src;
|
||||
size_t len;
|
||||
}
|
||||
CopyJob;
|
||||
|
||||
struct KVMGFXState
|
||||
{
|
||||
bool running;
|
||||
SDL_Window * window;
|
||||
SDL_Renderer * renderer;
|
||||
struct KVMGFXHeader * shm;
|
||||
|
||||
SDL_sem * cpySem;
|
||||
SDL_Thread * cpyThreads[COPY_THREADS];
|
||||
CopyJob cpyJobs [COPY_THREADS];
|
||||
};
|
||||
|
||||
struct KVMGFXState state;
|
||||
|
||||
int copyThread(void * arg)
|
||||
{
|
||||
CopyJob * job = (CopyJob *)arg;
|
||||
|
||||
while(state.running)
|
||||
{
|
||||
SDL_LockMutex(job->mutex);
|
||||
while(!job->rdy)
|
||||
SDL_CondWait(job->cond, job->mutex);
|
||||
job->rdy = false;
|
||||
SDL_UnlockMutex(job->mutex);
|
||||
|
||||
memcpy(job->dst, job->src, job->len);
|
||||
|
||||
// return a lock to the pool
|
||||
SDL_SemPost(state.cpySem);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool startCopyThreads()
|
||||
{
|
||||
state.cpySem = SDL_CreateSemaphore(COPY_THREADS);
|
||||
|
||||
for(int i = 0; i < COPY_THREADS; ++i)
|
||||
{
|
||||
// take a lock from the pool
|
||||
SDL_SemWait(state.cpySem);
|
||||
|
||||
CopyJob * job = &state.cpyJobs[i];
|
||||
job->mutex = SDL_CreateMutex();
|
||||
job->cond = SDL_CreateCond();
|
||||
job->rdy = false;
|
||||
job->dst = NULL;
|
||||
job->src = NULL;
|
||||
job->len = 0;
|
||||
|
||||
job->thread = SDL_CreateThread(
|
||||
copyThread, "copyThread", &state.cpyJobs[i]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void stopCopyThreads()
|
||||
{
|
||||
}
|
||||
|
||||
void compFunc_NONE(uint8_t * dst, const uint8_t * src, const size_t len)
|
||||
{
|
||||
const size_t part = len / COPY_THREADS;
|
||||
for(int i = 0; i < COPY_THREADS; ++i)
|
||||
{
|
||||
CopyJob * job = &state.cpyJobs[i];
|
||||
job->dst = dst + i * part;
|
||||
job->src = src + i * part;
|
||||
job->len = part;
|
||||
job->rdy = true;
|
||||
SDL_CondSignal(job->cond);
|
||||
}
|
||||
|
||||
// wait for the threads to complete
|
||||
for(int i = 0; i < COPY_THREADS; ++i)
|
||||
SDL_SemWait(state.cpySem);
|
||||
}
|
||||
|
||||
void compFunc_BLACK_RLE(uint8_t * dst, const uint8_t * src, const size_t len)
|
||||
{
|
||||
const size_t pixels = len / 3;
|
||||
for(size_t i = 0; i < pixels;)
|
||||
{
|
||||
if (!src[0] && !src[1] && !src[2])
|
||||
{
|
||||
struct RLEHeader * h = (struct RLEHeader *)src;
|
||||
dst += h->length * 3;
|
||||
i += h->length;
|
||||
src += sizeof(struct RLEHeader);
|
||||
continue;
|
||||
}
|
||||
|
||||
memcpy(dst, src, 3);
|
||||
dst += 3;
|
||||
src += 3;
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool areFormatsSame(const struct KVMGFXHeader s1, const struct KVMGFXHeader s2)
|
||||
{
|
||||
return
|
||||
(s1.version == s2.version ) &&
|
||||
(s1.frameType == s2.frameType) &&
|
||||
(s1.compType == s2.compType ) &&
|
||||
(s1.width == s2.width ) &&
|
||||
(s1.height == s2.height );
|
||||
}
|
||||
|
||||
void drawFunc_ARGB10(CompFunc compFunc, SDL_Texture * texture, uint8_t * dst, const uint8_t * src)
|
||||
{
|
||||
SDL_UpdateTexture(texture, NULL, src, state.shm->stride * 4);
|
||||
SDL_RenderClear(state.renderer);
|
||||
|
||||
SDL_RenderCopy(state.renderer, texture, NULL, NULL);
|
||||
SDL_RenderPresent(state.renderer);
|
||||
}
|
||||
|
||||
void drawFunc_ARGB(CompFunc compFunc, SDL_Texture * texture, uint8_t * dst, const uint8_t * src)
|
||||
{
|
||||
compFunc(dst, src, state.shm->height * state.shm->stride * 4);
|
||||
SDL_UnlockTexture(texture);
|
||||
SDL_RenderClear(state.renderer);
|
||||
|
||||
SDL_RenderCopy(state.renderer, texture, NULL, NULL);
|
||||
SDL_RenderPresent(state.renderer);
|
||||
}
|
||||
|
||||
void drawFunc_RGB(CompFunc compFunc, SDL_Texture * texture, uint8_t * dst, const uint8_t * src)
|
||||
{
|
||||
compFunc(dst, src, state.shm->height * state.shm->stride * 3);
|
||||
SDL_UnlockTexture(texture);
|
||||
SDL_RenderClear(state.renderer);
|
||||
|
||||
SDL_RenderCopy(state.renderer, texture, NULL, NULL);
|
||||
SDL_RenderPresent(state.renderer);
|
||||
}
|
||||
|
||||
void drawFunc_XOR(CompFunc compFunc, SDL_Texture * texture, uint8_t * dst, const uint8_t * src)
|
||||
{
|
||||
glEnable(GL_COLOR_LOGIC_OP);
|
||||
glLogicOp(GL_XOR);
|
||||
compFunc(dst, src, state.shm->height * state.shm->stride * 3);
|
||||
SDL_UnlockTexture(texture);
|
||||
if (state.shm->frames == 1)
|
||||
SDL_RenderClear(state.renderer);
|
||||
|
||||
SDL_RenderCopy(state.renderer, texture, NULL, NULL);
|
||||
SDL_RenderPresent(state.renderer);
|
||||
|
||||
// clear the buffer for the next frame
|
||||
memset(dst, 0, state.shm->height * state.shm->stride * 3);
|
||||
}
|
||||
|
||||
void drawFunc_YUV420P(CompFunc compFunc, SDL_Texture * texture, uint8_t * dst, const uint8_t * src)
|
||||
{
|
||||
const unsigned int pixels = state.shm->width * state.shm->height;
|
||||
|
||||
SDL_UpdateYUVTexture(texture, NULL,
|
||||
src , state.shm->stride,
|
||||
src + pixels , state.shm->stride / 2,
|
||||
src + pixels + pixels / 4, state.shm->stride / 2
|
||||
);
|
||||
SDL_RenderClear(state.renderer);
|
||||
|
||||
SDL_RenderCopy(state.renderer, texture, NULL, NULL);
|
||||
SDL_RenderPresent(state.renderer);
|
||||
}
|
||||
|
||||
int renderThread(void * unused)
|
||||
{
|
||||
bool startup = true;
|
||||
struct KVMGFXHeader format;
|
||||
SDL_Texture *texture = NULL;
|
||||
uint8_t *pixels = (uint8_t*)(state.shm + 1);
|
||||
uint8_t *texPixels = NULL;
|
||||
DrawFunc drawFunc = NULL;
|
||||
CompFunc compFunc = NULL;
|
||||
|
||||
format.version = 1;
|
||||
format.frameType = FRAME_TYPE_INVALID;
|
||||
format.width = 0;
|
||||
format.height = 0;
|
||||
format.stride = 0;
|
||||
format.frames = 0;
|
||||
|
||||
if (SDL_Init(SDL_INIT_VIDEO) < 0)
|
||||
{
|
||||
DEBUG_ERROR("SDL_Init Failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
state.window = SDL_CreateWindow("KVM-GFX Test", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 100, 100, SDL_WINDOW_BORDERLESS);
|
||||
if (!state.window)
|
||||
{
|
||||
DEBUG_ERROR("failed to create window");
|
||||
return -1;
|
||||
}
|
||||
|
||||
state.renderer = SDL_CreateRenderer(state.window , -1, 0);
|
||||
if (!state.renderer)
|
||||
{
|
||||
DEBUG_ERROR("failed to create window");
|
||||
return -1;
|
||||
}
|
||||
|
||||
startCopyThreads();
|
||||
|
||||
while(state.running)
|
||||
{
|
||||
// ensure the header magic is valid, this will help prevent crash out when the memory hasn't yet been initialized
|
||||
if (memcmp(state.shm->magic, KVMGFX_HEADER_MAGIC, sizeof(KVMGFX_HEADER_MAGIC)) != 0)
|
||||
continue;
|
||||
|
||||
// if the frame count hasn't changed, we don't do anything
|
||||
if (!startup && format.frames == state.shm->frames)
|
||||
{
|
||||
if (!state.running)
|
||||
break;
|
||||
|
||||
usleep(100);
|
||||
continue;
|
||||
}
|
||||
startup = false;
|
||||
|
||||
// if the format is invalid or it has changed
|
||||
if (format.frameType == FRAME_TYPE_INVALID || !areFormatsSame(format, *state.shm))
|
||||
{
|
||||
if (texture)
|
||||
{
|
||||
SDL_DestroyTexture(texture);
|
||||
texture = NULL;
|
||||
}
|
||||
|
||||
Uint32 sdlFormat;
|
||||
switch(state.shm->frameType)
|
||||
{
|
||||
case FRAME_TYPE_ARGB : sdlFormat = SDL_PIXELFORMAT_ARGB8888 ; drawFunc = drawFunc_ARGB ; break;
|
||||
case FRAME_TYPE_RGB : sdlFormat = SDL_PIXELFORMAT_RGB24 ; drawFunc = drawFunc_RGB ; break;
|
||||
case FRAME_TYPE_YUV420P: sdlFormat = SDL_PIXELFORMAT_YV12 ; drawFunc = drawFunc_YUV420P; break;
|
||||
case FRAME_TYPE_ARGB10 : sdlFormat = SDL_PIXELFORMAT_ARGB2101010; drawFunc = drawFunc_ARGB10 ; break;
|
||||
case FRAME_TYPE_XOR : sdlFormat = SDL_PIXELFORMAT_RGB24 ; drawFunc = drawFunc_XOR ; break;
|
||||
default:
|
||||
format.frameType = FRAME_TYPE_INVALID;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch(state.shm->compType)
|
||||
{
|
||||
case FRAME_COMP_NONE : compFunc = compFunc_NONE ; break;
|
||||
case FRAME_COMP_BLACK_RLE: compFunc = compFunc_BLACK_RLE; break;
|
||||
default:
|
||||
format.frameType = FRAME_TYPE_INVALID;
|
||||
continue;
|
||||
}
|
||||
|
||||
// update the window size and create the render texture
|
||||
SDL_SetWindowSize(state.window, state.shm->width, state.shm->height);
|
||||
SDL_SetWindowPosition(state.window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
||||
|
||||
texture = SDL_CreateTexture(state.renderer, sdlFormat, SDL_TEXTUREACCESS_STREAMING, state.shm->width, state.shm->height);
|
||||
|
||||
// this doesnt "lock" anything, pre-fetch the pointers for later use
|
||||
int unused;
|
||||
SDL_LockTexture(texture, NULL, (void**)&texPixels, &unused);
|
||||
|
||||
memcpy(&format, state.shm, sizeof(format));
|
||||
}
|
||||
|
||||
if (format.frames != state.shm->frames - 1)
|
||||
DEBUG_INFO("dropped %lu", state.shm->frames - format.frames);
|
||||
format.frames = state.shm->frames;
|
||||
|
||||
glDisable(GL_COLOR_LOGIC_OP);
|
||||
drawFunc(compFunc, texture, texPixels, pixels);
|
||||
|
||||
state.shm->clientFrame = format.frames;
|
||||
|
||||
// dont waste CPU, frames don't come that fast!
|
||||
usleep(10);
|
||||
}
|
||||
|
||||
SDL_DestroyTexture(texture);
|
||||
stopCopyThreads();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int spiceThread(void * arg)
|
||||
{
|
||||
while(state.running)
|
||||
if (!spice_process())
|
||||
{
|
||||
state.running = false;
|
||||
DEBUG_ERROR("Failed to process spice messages");
|
||||
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)
|
||||
{
|
||||
int mouseX = 0;
|
||||
int mouseY = 0;
|
||||
|
||||
// default to server mode
|
||||
bool serverMode = true;
|
||||
spice_mouse_mode(true);
|
||||
SDL_SetRelativeMouseMode(true);
|
||||
|
||||
// work around SDL_ShowCursor being non functional
|
||||
SDL_Cursor *cursor;
|
||||
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);
|
||||
|
||||
// ensure mouse acceleration is identical in server mode
|
||||
SDL_SetHintWithPriority(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, "1", SDL_HINT_OVERRIDE);
|
||||
|
||||
SDL_Event event;
|
||||
while(state.running)
|
||||
{
|
||||
while(SDL_PollEvent(&event))
|
||||
{
|
||||
switch(event.type)
|
||||
{
|
||||
case SDL_QUIT:
|
||||
state.running = false;
|
||||
break;
|
||||
|
||||
case SDL_KEYDOWN:
|
||||
{
|
||||
SDL_Scancode sc = event.key.keysym.scancode;
|
||||
if (sc == SDL_SCANCODE_SCROLLLOCK)
|
||||
{
|
||||
serverMode = !serverMode;
|
||||
spice_mouse_mode(serverMode);
|
||||
SDL_SetRelativeMouseMode(serverMode);
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t scancode = mapScancode(sc);
|
||||
if (scancode == 0)
|
||||
break;
|
||||
|
||||
spice_key_down(scancode);
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_KEYUP:
|
||||
{
|
||||
SDL_Scancode sc = event.key.keysym.scancode;
|
||||
if (sc == SDL_SCANCODE_SCROLLLOCK)
|
||||
break;
|
||||
|
||||
|
||||
uint32_t scancode = mapScancode(sc);
|
||||
if (scancode == 0)
|
||||
break;
|
||||
|
||||
spice_key_up(scancode);
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_MOUSEWHEEL:
|
||||
spice_mouse_press (event.wheel.y == 1 ? 4 : 5);
|
||||
spice_mouse_release(event.wheel.y == 1 ? 4 : 5);
|
||||
break;
|
||||
|
||||
case SDL_MOUSEMOTION:
|
||||
if (serverMode)
|
||||
spice_mouse_motion(event.motion.xrel, event.motion.yrel);
|
||||
else
|
||||
spice_mouse_motion(
|
||||
(int)event.motion.x - mouseX,
|
||||
(int)event.motion.y - mouseY
|
||||
);
|
||||
|
||||
mouseX = event.motion.x;
|
||||
mouseY = event.motion.y;
|
||||
break;
|
||||
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
spice_mouse_position(event.button.x, event.button.y);
|
||||
spice_mouse_press(event.button.button);
|
||||
break;
|
||||
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
spice_mouse_position(event.button.x, event.button.y);
|
||||
spice_mouse_release(event.button.button);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
usleep(1000);
|
||||
}
|
||||
|
||||
SDL_FreeCursor(cursor);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
memset(&state, 0, sizeof(state));
|
||||
state.running = true;
|
||||
|
||||
int shm_fd = 0;
|
||||
SDL_Thread *t_spice = NULL;
|
||||
SDL_Thread *t_event = NULL;
|
||||
|
||||
while(1)
|
||||
{
|
||||
umask(0);
|
||||
const mode_t mode =
|
||||
S_IRUSR | S_IWUSR |
|
||||
S_IRGRP | S_IWGRP |
|
||||
S_IROTH | S_IWOTH;
|
||||
|
||||
if ((shm_fd = shm_open("ivshmem", O_CREAT | O_RDWR, mode)) < 0)
|
||||
{
|
||||
DEBUG_ERROR("failed to open shared memory: %d %s", errno, strerror(errno));
|
||||
break;
|
||||
}
|
||||
|
||||
if (ftruncate(shm_fd, MAP_SIZE) != 0)
|
||||
{
|
||||
DEBUG_ERROR("failed to truncate memory region");
|
||||
break;
|
||||
}
|
||||
|
||||
if (!spice_connect("127.0.0.1", 5900, ""))
|
||||
{
|
||||
DEBUG_ERROR("Failed to connect to spice server");
|
||||
return 0;
|
||||
}
|
||||
|
||||
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)))
|
||||
{
|
||||
DEBUG_ERROR("spice create thread failed");
|
||||
break;
|
||||
}
|
||||
|
||||
state.shm = (struct KVMGFXHeader *)mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
|
||||
if (!state.shm)
|
||||
{
|
||||
DEBUG_ERROR("Failed to map memory");
|
||||
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);
|
||||
|
||||
if (t_spice)
|
||||
SDL_WaitThread(t_spice, NULL);
|
||||
|
||||
if (state.renderer)
|
||||
SDL_DestroyRenderer(state.renderer);
|
||||
|
||||
if (state.window)
|
||||
SDL_DestroyWindow(state.window);
|
||||
|
||||
if (state.shm)
|
||||
munmap(state.shm, MAP_SIZE);
|
||||
|
||||
if (shm_fd)
|
||||
close(shm_fd);
|
||||
//shm_unlink("kvm-windows");
|
||||
|
||||
SDL_Quit();
|
||||
return 0;
|
||||
}
|
1
client/spice-common
Submodule
1
client/spice-common
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 739a859d79428fe188cd6516508ba013a162a810
|
787
client/spice.c
Normal file
787
client/spice.c
Normal file
@ -0,0 +1,787 @@
|
||||
#include "spice.h"
|
||||
|
||||
#define DEBUG
|
||||
#include "debug.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/select.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <spice/protocol.h>
|
||||
#include <spice/error_codes.h>
|
||||
|
||||
#include "spice/messages.h"
|
||||
|
||||
// ============================================================================
|
||||
|
||||
// internal structures
|
||||
struct SpiceChannel
|
||||
{
|
||||
bool connected;
|
||||
bool initDone;
|
||||
uint8_t channelType;
|
||||
int socket;
|
||||
uint32_t ackFrequency;
|
||||
uint32_t ackCount;
|
||||
uint32_t serial;
|
||||
};
|
||||
|
||||
struct SpiceKeyboard
|
||||
{
|
||||
uint32_t modifiers;
|
||||
};
|
||||
|
||||
struct SpiceMouse
|
||||
{
|
||||
uint32_t buttonState;
|
||||
};
|
||||
|
||||
struct Spice
|
||||
{
|
||||
char password[32];
|
||||
struct sockaddr_in addr;
|
||||
|
||||
uint32_t sessionID;
|
||||
uint32_t channelID;
|
||||
struct SpiceChannel scMain;
|
||||
struct SpiceChannel scInputs;
|
||||
|
||||
struct SpiceKeyboard kb;
|
||||
struct SpiceMouse mouse;
|
||||
};
|
||||
|
||||
// globals
|
||||
struct Spice spice =
|
||||
{
|
||||
.sessionID = 0,
|
||||
.scMain .connected = false,
|
||||
.scMain .channelType = SPICE_CHANNEL_MAIN,
|
||||
.scInputs.connected = false,
|
||||
.scInputs.channelType = SPICE_CHANNEL_INPUTS,
|
||||
};
|
||||
|
||||
// internal forward decls
|
||||
bool spice_connect_channel (struct SpiceChannel * channel);
|
||||
void spice_disconnect_channel(struct SpiceChannel * channel);
|
||||
|
||||
bool spice_process_ack(struct SpiceChannel * channel);
|
||||
|
||||
bool spice_on_common_read (struct SpiceChannel * channel, SpiceDataHeader * header, bool * handled);
|
||||
bool spice_on_main_channel_read ();
|
||||
bool spice_on_inputs_channel_read();
|
||||
|
||||
bool spice_read (const struct SpiceChannel * channel, void * buffer, const ssize_t size);
|
||||
ssize_t spice_write (const struct SpiceChannel * channel, const void * buffer, const ssize_t size);
|
||||
bool spice_write_msg(struct SpiceChannel * channel, uint32_t type, const void * buffer, const ssize_t size);
|
||||
bool spice_discard (const struct SpiceChannel * channel, ssize_t size);
|
||||
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_connect(const char * host, const short port, const char * password)
|
||||
{
|
||||
strncpy(spice.password, password, sizeof(spice.password));
|
||||
memset(&spice.addr, 0, sizeof(struct sockaddr_in));
|
||||
inet_pton(AF_INET, host, &spice.addr.sin_addr);
|
||||
spice.addr.sin_family = AF_INET;
|
||||
spice.addr.sin_port = htons(port);
|
||||
|
||||
spice.channelID = 0;
|
||||
if (!spice_connect_channel(&spice.scMain))
|
||||
{
|
||||
DEBUG_ERROR("connect main channel failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
void spice_disconnect()
|
||||
{
|
||||
spice_disconnect_channel(&spice.scMain );
|
||||
spice_disconnect_channel(&spice.scInputs);
|
||||
|
||||
spice.sessionID = 0;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_ready()
|
||||
{
|
||||
return spice.scMain.connected &&
|
||||
spice.scInputs.connected;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_process()
|
||||
{
|
||||
fd_set readSet;
|
||||
FD_ZERO(&readSet);
|
||||
FD_SET(spice.scMain.socket , &readSet);
|
||||
FD_SET(spice.scInputs.socket, &readSet);
|
||||
|
||||
struct timeval timeout;
|
||||
timeout.tv_sec = 1;
|
||||
timeout.tv_usec = 0;
|
||||
|
||||
int rc = select(FD_SETSIZE, &readSet, NULL, NULL, &timeout);
|
||||
if (rc < 0)
|
||||
{
|
||||
DEBUG_ERROR("select failure");
|
||||
return false;
|
||||
}
|
||||
|
||||
for(int i = 0; i < FD_SETSIZE; ++i)
|
||||
if (FD_ISSET(i, &readSet))
|
||||
{
|
||||
if (i == spice.scMain.socket)
|
||||
{
|
||||
if (spice_on_main_channel_read())
|
||||
{
|
||||
if (spice.scMain.connected && !spice_process_ack(&spice.scMain))
|
||||
{
|
||||
DEBUG_ERROR("failed to process ack on main channel");
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_ERROR("failed to perform read on main channel");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (spice.scInputs.connected && i == spice.scInputs.socket)
|
||||
{
|
||||
if (spice_on_inputs_channel_read())
|
||||
{
|
||||
if (!spice_process_ack(&spice.scInputs))
|
||||
{
|
||||
DEBUG_ERROR("failed to process ack on inputs channel");
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_ERROR("failed to perform read on inputs channel");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_process_ack(struct SpiceChannel * channel)
|
||||
{
|
||||
if (channel->ackFrequency == 0)
|
||||
return true;
|
||||
|
||||
if (channel->ackCount++ != channel->ackFrequency)
|
||||
return true;
|
||||
|
||||
channel->ackCount = 0;
|
||||
return spice_write_msg(channel, SPICE_MSGC_ACK, "\0", 1);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_on_common_read(struct SpiceChannel * channel, SpiceDataHeader * header, bool * handled)
|
||||
{
|
||||
if (!spice_read(channel, header, sizeof(SpiceDataHeader)))
|
||||
{
|
||||
DEBUG_ERROR("read failure");
|
||||
*handled = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
#if 0
|
||||
printf("socket: %d, serial: %6u, type: %2u, size %6u, sub_list %4u\n",
|
||||
channel->socket,
|
||||
header->serial, header->type, header->size, header->sub_list);
|
||||
#endif
|
||||
|
||||
if (!channel->initDone)
|
||||
{
|
||||
*handled = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
switch(header->type)
|
||||
{
|
||||
case SPICE_MSG_MIGRATE:
|
||||
case SPICE_MSG_MIGRATE_DATA:
|
||||
{
|
||||
DEBUG_PROTO("SPICE_MSG_MIGRATE_DATA");
|
||||
|
||||
*handled = true;
|
||||
DEBUG_WARN("migration is not supported");
|
||||
return false;
|
||||
}
|
||||
|
||||
case SPICE_MSG_SET_ACK:
|
||||
{
|
||||
DEBUG_INFO("SPICE_MSG_SET_ACK");
|
||||
*handled = true;
|
||||
|
||||
SpiceMsgSetAck in;
|
||||
if (!spice_read(channel, &in, sizeof(in)))
|
||||
return false;
|
||||
|
||||
channel->ackFrequency = in.window;
|
||||
|
||||
SpiceMsgcAckSync out;
|
||||
out.generation = in.generation;
|
||||
if (!spice_write_msg(channel, SPICE_MSGC_ACK_SYNC, &out, sizeof(out)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case SPICE_MSG_PING:
|
||||
{
|
||||
DEBUG_PROTO("SPICE_MSG_PING");
|
||||
*handled = true;
|
||||
|
||||
SpiceMsgPing in;
|
||||
if (!spice_read(channel, &in, sizeof(in)))
|
||||
return false;
|
||||
|
||||
if (!spice_discard(channel, header->size - sizeof(in)))
|
||||
{
|
||||
DEBUG_ERROR("failed discarding enough bytes from the ping packet");
|
||||
return false;
|
||||
}
|
||||
|
||||
SpiceMsgcPong out;
|
||||
out.id = in.id;
|
||||
out.timestamp = in.timestamp;
|
||||
if (!spice_write_msg(channel, SPICE_MSGC_PONG, &out, sizeof(out)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case SPICE_MSG_WAIT_FOR_CHANNELS:
|
||||
case SPICE_MSG_DISCONNECTING :
|
||||
{
|
||||
*handled = true;
|
||||
DEBUG_FIXME("ignored wait-for-channels or disconnect message");
|
||||
return false;
|
||||
}
|
||||
|
||||
case SPICE_MSG_NOTIFY:
|
||||
{
|
||||
DEBUG_PROTO("SPICE_MSG_NOTIFY");
|
||||
SpiceMsgNotify in;
|
||||
if (!spice_read(channel, &in, sizeof(in)))
|
||||
return false;
|
||||
|
||||
char msg[in.message_len+1];
|
||||
if (!spice_read(channel, msg, in.message_len+1))
|
||||
return false;
|
||||
|
||||
DEBUG_INFO("notify message: %s", msg);
|
||||
*handled = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
*handled = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_on_main_channel_read()
|
||||
{
|
||||
struct SpiceChannel *channel = &spice.scMain;
|
||||
|
||||
SpiceDataHeader header;
|
||||
bool handled;
|
||||
|
||||
if (!spice_on_common_read(channel, &header, &handled))
|
||||
{
|
||||
DEBUG_ERROR("read failure");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (handled)
|
||||
return true;
|
||||
|
||||
if (!channel->initDone)
|
||||
{
|
||||
if (header.type != SPICE_MSG_MAIN_INIT)
|
||||
{
|
||||
spice_disconnect();
|
||||
DEBUG_ERROR("expected main init message but got type %u", header.type);
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_PROTO("SPICE_MSG_MAIN_INIT");
|
||||
|
||||
channel->initDone = true;
|
||||
SpiceMsgMainInit msg;
|
||||
if (!spice_read(channel, &msg, sizeof(msg)))
|
||||
{
|
||||
spice_disconnect();
|
||||
return false;
|
||||
}
|
||||
|
||||
spice.sessionID = msg.session_id;
|
||||
if (msg.current_mouse_mode != SPICE_MOUSE_MODE_CLIENT && !spice_mouse_mode(false))
|
||||
{
|
||||
DEBUG_ERROR("failed to set mouse mode");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!spice_connect_channel(&spice.scInputs))
|
||||
{
|
||||
DEBUG_ERROR("failed to connect inputs channel");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
DEBUG_WARN("main channel unhandled message type %u", header.type);
|
||||
spice_discard(channel, header.size);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_on_inputs_channel_read()
|
||||
{
|
||||
struct SpiceChannel *channel = &spice.scInputs;
|
||||
|
||||
SpiceDataHeader header;
|
||||
bool handled;
|
||||
|
||||
if (!spice_on_common_read(channel, &header, &handled))
|
||||
{
|
||||
DEBUG_ERROR("read failure");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (handled)
|
||||
return true;
|
||||
|
||||
switch(header.type)
|
||||
{
|
||||
case SPICE_MSG_INPUTS_INIT:
|
||||
{
|
||||
DEBUG_PROTO("SPICE_MSG_INPUTS_INIT");
|
||||
|
||||
if (channel->initDone)
|
||||
{
|
||||
DEBUG_ERROR("input init message already done");
|
||||
return false;
|
||||
}
|
||||
|
||||
channel->initDone = true;
|
||||
|
||||
SpiceMsgInputsInit in;
|
||||
if (!spice_read(channel, &in, sizeof(in)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case SPICE_MSG_INPUTS_KEY_MODIFIERS:
|
||||
{
|
||||
DEBUG_PROTO("SPICE_MSG_INPUTS_KEY_MODIFIERS");
|
||||
SpiceMsgInputsInit in;
|
||||
if (!spice_read(channel, &in, sizeof(in)))
|
||||
return false;
|
||||
|
||||
spice.kb.modifiers = in.modifiers;
|
||||
return true;
|
||||
}
|
||||
|
||||
case SPICE_MSG_INPUTS_MOUSE_MOTION_ACK:
|
||||
{
|
||||
DEBUG_PROTO("SPICE_MSG_INPUTS_MOUSE_MOTION_ACK");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG_WARN("inputs channel unhandled message type %u", header.type);
|
||||
spice_discard(channel, header.size);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_connect_channel(struct SpiceChannel * channel)
|
||||
{
|
||||
channel->initDone = false;
|
||||
channel->ackFrequency = 0;
|
||||
channel->ackCount = 0;
|
||||
channel->serial = 0;
|
||||
|
||||
channel->socket = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (channel->socket == -1)
|
||||
return false;
|
||||
|
||||
int flag = 1;
|
||||
setsockopt(channel->socket, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(int));
|
||||
|
||||
if (connect(channel->socket, (const struct sockaddr *)&spice.addr, sizeof(spice.addr)) == -1)
|
||||
{
|
||||
DEBUG_ERROR("socket connect failure");
|
||||
close(channel->socket);
|
||||
return false;
|
||||
}
|
||||
channel->connected = true;
|
||||
|
||||
SpiceLinkHeader header =
|
||||
{
|
||||
.magic = SPICE_MAGIC ,
|
||||
.major_version = SPICE_VERSION_MAJOR,
|
||||
.minor_version = SPICE_VERSION_MINOR,
|
||||
.size = sizeof(SpiceLinkMess)
|
||||
};
|
||||
|
||||
SpiceLinkMess message =
|
||||
{
|
||||
.connection_id = spice.sessionID,
|
||||
.channel_type = channel->channelType,
|
||||
.channel_id = spice.channelID,
|
||||
.num_common_caps = 0,
|
||||
.num_channel_caps = 0,
|
||||
.caps_offset = sizeof(SpiceLinkMess)
|
||||
};
|
||||
|
||||
spice_write(channel, &header , sizeof(header ));
|
||||
spice_write(channel, &message, sizeof(message));
|
||||
|
||||
if (!spice_read(channel, &header, sizeof(header)))
|
||||
{
|
||||
DEBUG_ERROR("failed to read SpiceLinkHeader");
|
||||
spice_disconnect_channel(channel);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header.magic != SPICE_MAGIC || header.major_version != SPICE_VERSION_MAJOR)
|
||||
{
|
||||
DEBUG_ERROR("invalid or unsupported protocol version");
|
||||
spice_disconnect_channel(channel);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header.size < sizeof(SpiceLinkReply))
|
||||
{
|
||||
DEBUG_ERROR("reported data size too small");
|
||||
spice_disconnect_channel(channel);
|
||||
return false;
|
||||
}
|
||||
|
||||
SpiceLinkReply reply;
|
||||
if (!spice_read(channel, &reply, sizeof(reply)))
|
||||
{
|
||||
DEBUG_ERROR("failed to read SpiceLinkReply");
|
||||
spice_disconnect_channel(channel);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (reply.error != SPICEC_ERROR_CODE_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("server replied with error %u", reply.error);
|
||||
spice_disconnect_channel(channel);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t capsCommon [reply.num_common_caps ];
|
||||
uint32_t capsChannel[reply.num_channel_caps];
|
||||
spice_read(channel, &capsCommon , sizeof(capsCommon ));
|
||||
spice_read(channel, &capsChannel, sizeof(capsChannel));
|
||||
|
||||
BIO *bioKey = BIO_new(BIO_s_mem());
|
||||
if (!bioKey)
|
||||
{
|
||||
DEBUG_ERROR("failed to allocate bioKey");
|
||||
spice_disconnect_channel(channel);
|
||||
return false;
|
||||
}
|
||||
|
||||
BIO_write(bioKey, reply.pub_key, SPICE_TICKET_PUBKEY_BYTES);
|
||||
EVP_PKEY *rsaKey = d2i_PUBKEY_bio(bioKey, NULL);
|
||||
RSA *rsa = EVP_PKEY_get1_RSA(rsaKey);
|
||||
|
||||
char enc[RSA_size(rsa)];
|
||||
if (RSA_public_encrypt(
|
||||
strlen(spice.password) + 1,
|
||||
(uint8_t*)spice.password,
|
||||
(uint8_t*)enc,
|
||||
rsa,
|
||||
RSA_PKCS1_OAEP_PADDING
|
||||
) <= 0)
|
||||
{
|
||||
DEBUG_ERROR("rsa public encrypt failed");
|
||||
spice_disconnect_channel(channel);
|
||||
EVP_PKEY_free(rsaKey);
|
||||
BIO_free(bioKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t rsaSize = RSA_size(rsa);
|
||||
EVP_PKEY_free(rsaKey);
|
||||
BIO_free(bioKey);
|
||||
|
||||
if (!spice_write(channel, enc, rsaSize))
|
||||
{
|
||||
DEBUG_ERROR("failed to write encrypted data");
|
||||
spice_disconnect_channel(channel);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t linkResult;
|
||||
if (!spice_read(channel, &linkResult, sizeof(linkResult)))
|
||||
{
|
||||
DEBUG_ERROR("failed to read SpiceLinkResult");
|
||||
spice_disconnect_channel(channel);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (linkResult != SPICE_LINK_ERR_OK)
|
||||
{
|
||||
DEBUG_ERROR("connect code error %u", linkResult);
|
||||
spice_disconnect_channel(channel);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
void spice_disconnect_channel(struct SpiceChannel * channel)
|
||||
{
|
||||
if (channel->connected)
|
||||
close(channel->socket);
|
||||
channel->connected = false;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
ssize_t spice_write(const struct SpiceChannel * channel, const void * buffer, const ssize_t size)
|
||||
{
|
||||
if (!channel->connected)
|
||||
{
|
||||
DEBUG_ERROR("not connected");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!buffer)
|
||||
{
|
||||
DEBUG_ERROR("invalid buffer argument supplied");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ssize_t len = send(channel->socket, buffer, size, 0);
|
||||
if (len != size)
|
||||
DEBUG_WARN("incomplete write");
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_write_msg(struct SpiceChannel * channel, uint32_t type, const void * buffer, const ssize_t size)
|
||||
{
|
||||
SpiceDataHeader header;
|
||||
header.serial = channel->serial++;
|
||||
header.type = type;
|
||||
header.size = size;
|
||||
header.sub_list = 0;
|
||||
|
||||
if (spice_write(channel, &header, sizeof(header)) != sizeof(header))
|
||||
{
|
||||
DEBUG_ERROR("failed to write message header");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (spice_write(channel, buffer, size) != size)
|
||||
{
|
||||
DEBUG_ERROR("failed to write message body");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_read(const struct SpiceChannel * channel, void * buffer, const ssize_t size)
|
||||
{
|
||||
if (!channel->connected)
|
||||
{
|
||||
DEBUG_ERROR("not connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!buffer)
|
||||
{
|
||||
DEBUG_ERROR("invalid buffer argument supplied");
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t len = read(channel->socket, buffer, size);
|
||||
if (len != size)
|
||||
{
|
||||
DEBUG_ERROR("incomplete write");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_discard(const struct SpiceChannel * channel, ssize_t size)
|
||||
{
|
||||
while(size)
|
||||
{
|
||||
char c[8192];
|
||||
size_t len = read(channel->socket, c, size > sizeof(c) ? sizeof(c) : size);
|
||||
if (len <= 0)
|
||||
return false;
|
||||
|
||||
size -= len;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_key_down(uint32_t code)
|
||||
{
|
||||
DEBUG_PROTO("%u", code);
|
||||
|
||||
if (code > 0x100)
|
||||
code = 0xe0 | ((code - 0x100) << 8);
|
||||
|
||||
SpiceMsgcKeyDown msg;
|
||||
msg.code = code;
|
||||
|
||||
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_KEY_DOWN, &msg, sizeof(msg));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_key_up(uint32_t code)
|
||||
{
|
||||
DEBUG_PROTO("%u", code);
|
||||
|
||||
if (code < 0x100)
|
||||
code |= 0x80;
|
||||
else
|
||||
code = 0x80e0 | ((code - 0x100) << 8);
|
||||
|
||||
SpiceMsgcKeyDown msg;
|
||||
msg.code = code;
|
||||
|
||||
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_KEY_UP, &msg, sizeof(msg));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_mouse_mode(bool server)
|
||||
{
|
||||
DEBUG_PROTO("%s", server ? "server" : "client");
|
||||
|
||||
SpiceMsgcMainMouseModeRequest msg;
|
||||
msg.mouse_mode = server ? SPICE_MOUSE_MODE_SERVER : SPICE_MOUSE_MODE_CLIENT;
|
||||
|
||||
return spice_write_msg(&spice.scMain, SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST, &msg, sizeof(msg));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_mouse_position(uint32_t x, uint32_t y)
|
||||
{
|
||||
DEBUG_PROTO("x=%u, y=%u", x, y);
|
||||
|
||||
SpiceMsgcMousePosition msg;
|
||||
msg.x = x;
|
||||
msg.y = y;
|
||||
msg.button_state = spice.mouse.buttonState;
|
||||
msg.display_id = 0;
|
||||
|
||||
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_MOUSE_POSITION, &msg, sizeof(msg));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_mouse_motion(int32_t x, int32_t y)
|
||||
{
|
||||
DEBUG_PROTO("x=%d, y=%d", x, y);
|
||||
|
||||
SpiceMsgcMouseMotion msg;
|
||||
msg.x = x;
|
||||
msg.y = y;
|
||||
msg.button_state = spice.mouse.buttonState;
|
||||
|
||||
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_MOUSE_MOTION, &msg, sizeof(msg));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_mouse_press(uint32_t button)
|
||||
{
|
||||
DEBUG_PROTO("%u", button);
|
||||
|
||||
switch(button)
|
||||
{
|
||||
case SPICE_MOUSE_BUTTON_LEFT : spice.mouse.buttonState |= SPICE_MOUSE_BUTTON_MASK_LEFT ; break;
|
||||
case SPICE_MOUSE_BUTTON_MIDDLE: spice.mouse.buttonState |= SPICE_MOUSE_BUTTON_MASK_MIDDLE; break;
|
||||
case SPICE_MOUSE_BUTTON_RIGHT : spice.mouse.buttonState |= SPICE_MOUSE_BUTTON_MASK_RIGHT ; break;
|
||||
}
|
||||
|
||||
SpiceMsgcMousePress msg;
|
||||
msg.button = button;
|
||||
msg.button_state = spice.mouse.buttonState;
|
||||
|
||||
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_MOUSE_PRESS, &msg, sizeof(msg));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
bool spice_mouse_release(uint32_t button)
|
||||
{
|
||||
DEBUG_PROTO("%u", button);
|
||||
|
||||
switch(button)
|
||||
{
|
||||
case SPICE_MOUSE_BUTTON_LEFT : spice.mouse.buttonState &= ~SPICE_MOUSE_BUTTON_MASK_LEFT ; break;
|
||||
case SPICE_MOUSE_BUTTON_MIDDLE: spice.mouse.buttonState &= ~SPICE_MOUSE_BUTTON_MASK_MIDDLE; break;
|
||||
case SPICE_MOUSE_BUTTON_RIGHT : spice.mouse.buttonState &= ~SPICE_MOUSE_BUTTON_MASK_RIGHT ; break;
|
||||
}
|
||||
|
||||
SpiceMsgcMouseRelease msg;
|
||||
msg.button = button;
|
||||
msg.button_state = spice.mouse.buttonState;
|
||||
|
||||
return spice_write_msg(&spice.scInputs, SPICE_MSGC_INPUTS_MOUSE_RELEASE, &msg, sizeof(msg));
|
||||
}
|
16
client/spice.h
Normal file
16
client/spice.h
Normal file
@ -0,0 +1,16 @@
|
||||
#include <sys/types.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
bool spice_connect(const char * host, const short port, const char * password);
|
||||
void spice_disconnect();
|
||||
bool spice_process();
|
||||
bool spice_ready();
|
||||
|
||||
bool spice_key_down (uint32_t code);
|
||||
bool spice_key_up (uint32_t code);
|
||||
bool spice_mouse_mode (bool server);
|
||||
bool spice_mouse_position(uint32_t x, uint32_t y);
|
||||
bool spice_mouse_motion ( int32_t x, int32_t y);
|
||||
bool spice_mouse_press (uint32_t button);
|
||||
bool spice_mouse_release (uint32_t button);
|
102
client/spice/messages.h
Normal file
102
client/spice/messages.h
Normal file
@ -0,0 +1,102 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#pragma pack(push,1)
|
||||
|
||||
typedef struct SpicePoint16
|
||||
{
|
||||
int16_t x, y;
|
||||
}
|
||||
SpicePoint16;
|
||||
|
||||
typedef struct SpiceMsgMainInit
|
||||
{
|
||||
uint32_t session_id;
|
||||
uint32_t display_channels_hint;
|
||||
uint32_t supported_mouse_modes;
|
||||
uint32_t current_mouse_mode;
|
||||
uint32_t agent_connected;
|
||||
uint32_t agent_tokens;
|
||||
uint32_t multi_media_time;
|
||||
uint32_t ram_hint;
|
||||
}
|
||||
SpiceMsgMainInit;
|
||||
|
||||
typedef struct SpiceMsgcMainMouseModeRequest
|
||||
{
|
||||
uint16_t mouse_mode;
|
||||
}
|
||||
SpiceMsgcMainMouseModeRequest;
|
||||
|
||||
typedef struct SpiceMsgPing
|
||||
{
|
||||
uint32_t id;
|
||||
uint64_t timestamp;
|
||||
}
|
||||
SpiceMsgPing,
|
||||
SpiceMsgcPong;
|
||||
|
||||
typedef struct SpiceMsgSetAck
|
||||
{
|
||||
uint32_t generation;
|
||||
uint32_t window;
|
||||
}
|
||||
SpiceMsgSetAck;
|
||||
|
||||
typedef struct SpiceMsgcAckSync
|
||||
{
|
||||
uint32_t generation;
|
||||
}
|
||||
SpiceMsgcAckSync;
|
||||
|
||||
typedef struct SpiceMsgNotify
|
||||
{
|
||||
uint64_t time_stamp;
|
||||
uint32_t severity;
|
||||
uint32_t visibility;
|
||||
uint32_t what;
|
||||
uint32_t message_len;
|
||||
//char message[message_len+1]
|
||||
}
|
||||
SpiceMsgNotify;
|
||||
|
||||
typedef struct SpiceMsgInputsInit
|
||||
{
|
||||
uint16_t modifiers;
|
||||
}
|
||||
SpiceMsgInputsInit,
|
||||
SpiceMsgInputsKeyModifiers,
|
||||
SpiceMsgcInputsKeyModifiers;
|
||||
|
||||
typedef struct SpiceMsgcKeyDown
|
||||
{
|
||||
uint32_t code;
|
||||
}
|
||||
SpiceMsgcKeyDown,
|
||||
SpiceMsgcKeyUp;
|
||||
|
||||
typedef struct SpiceMsgcMousePosition
|
||||
{
|
||||
uint32_t x;
|
||||
uint32_t y;
|
||||
uint16_t button_state;
|
||||
uint8_t display_id;
|
||||
}
|
||||
SpiceMsgcMousePosition;
|
||||
|
||||
typedef struct SpiceMsgcMouseMotion
|
||||
{
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
uint16_t button_state;
|
||||
}
|
||||
SpiceMsgcMouseMotion;
|
||||
|
||||
typedef struct SpiceMsgcMousePress
|
||||
{
|
||||
uint8_t button;
|
||||
uint16_t button_state;
|
||||
}
|
||||
SpiceMsgcMousePress,
|
||||
SpiceMsgcMouseRelease;
|
||||
|
||||
#pragma pack(pop)
|
0
server/TODO
Normal file
0
server/TODO
Normal file
Loading…
Reference in New Issue
Block a user