Compare commits

..

28 Commits
B2 ... porthole

Author SHA1 Message Date
Geoffrey McRae
ce5c36705e [porthole] fix invalid pointer dereference 2019-11-12 16:51:04 +11:00
Geoffrey McRae
7aee4eed11 [porthole] added missing file and adjusted linux code 2019-11-12 15:55:13 +11:00
Geoffrey McRae
416c4bbb48 [porthole] added missing include 2019-11-12 15:50:12 +11:00
Geoffrey McRae
76007092d5 [porthole] make the segments private and add a method to obtain the ptr 2019-11-12 15:43:44 +11:00
Geoffrey McRae
453b8e6a4d [porthole] added connection state support 2019-11-12 15:18:53 +11:00
Geoffrey McRae
968b313993 [porthole] updated windows driver defines 2019-11-12 13:08:37 +11:00
Geoffrey McRae
4dde82c646 [porthole] process all messages sent from qemu 2019-11-05 14:56:07 +11:00
Geoffrey McRae
4c424cdbdf [porthole] fix unmap control flow 2019-11-05 01:30:33 +11:00
Geoffrey McRae
963e7f8393 [porthole] prevent double include of headers 2019-11-05 01:09:11 +11:00
Geoffrey McRae
f93c918aa5 [porthole] added memory copy utility functions 2019-11-05 00:50:39 +11:00
Geoffrey McRae
d5f409b02e [common] add include for abort to debug.h 2019-11-05 00:20:05 +11:00
Geoffrey McRae
75cea21cfc [porthole] added receive timeout to allow for clean shutdown 2019-11-04 23:24:34 +11:00
Geoffrey McRae
df2a3b6151 [porthole] bug fixes 2019-11-04 23:07:26 +11:00
Geoffrey McRae
fad4d18973 [porthole] added missing header 2019-11-04 22:42:17 +11:00
Geoffrey McRae
f4ad730cc4 [arbiter] initial arbiter program for porthole communications 2019-11-04 22:39:27 +11:00
Geoffrey McRae
67ddb70932 [porthole] link pthreads and fix function type 2019-11-04 22:25:19 +11:00
Geoffrey McRae
27c3a93d15 [porthole] added unmap logic and response 2019-11-04 22:05:50 +11:00
Geoffrey McRae
df9798c819 [common] added objectlist_pop and objectlist_remove methods 2019-11-04 22:05:21 +11:00
Geoffrey McRae
1dfa0ed218 [common] added missing file to the repository 2019-11-04 21:10:21 +11:00
Geoffrey McRae
01f5238a9d [porthole] initial client implementation 2019-11-04 21:09:13 +11:00
Geoffrey McRae
c382a5acb1 [common] objectlists store void* not char* 2019-11-04 21:08:29 +11:00
Geoffrey McRae
5e3a46beb9 [common] add DEBUG_FATAL 2019-11-04 21:08:17 +11:00
Geoffrey McRae
6ed4e23b80 [common] fix objectlist_push type 2019-11-04 17:41:12 +11:00
Geoffrey McRae
0851ae6f14 [common] converted stringlist to a generic objectlist 2019-11-04 16:41:57 +11:00
Geoffrey McRae
caebddce4d [porthole] cosmetics, remove tabs 2019-10-31 23:46:46 +11:00
Geoffrey McRae
01da541815 [porthole] update in accordance with the recent windows driver changes 2019-10-31 23:45:08 +11:00
Geoffrey McRae
9d6bb57eff [porthole] cosmetics: remove tabs 2019-10-30 17:39:27 +11:00
Geoffrey McRae
438548c427 [porthole] initial implementation of the porthole device interface
This is known as 'introspection' in the gnif/qemu repo, it's name is not
final, however porthole is more appropriate but also may not be the
final name.

Note: This branch is experiemental and may never be released if QEMU do
not accept the patch for the new device upstream.
2019-10-30 17:28:13 +11:00
134 changed files with 6610 additions and 6902 deletions

12
.github/FUNDING.yml vendored
View File

@@ -1,12 +0,0 @@
# These are supported funding model platforms
github: gnif
patreon: gnif
open_collective: # Replace with a single Open Collective username
ko_fi: lookingglass
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -1,69 +1,11 @@
### Issues are for Bug Reports and Feature Requests Only! ### Required information
If you are looking for help or support please use one of the following methods Host CPU:
Host GPU:
Guest GPU:
Host Kernel version:
Host QEMU version:
Create a New Topic on the Level1Tech's forum under the Looking Glass category: Please describe what were you doing when the problem occured. If the Windows host application crashed please check for file named `looking-glass-host.dmp` and attach it to this bug report.
* https://forum.level1techs.com/c/software/lookingGlass/142
Ask for help in #looking-glass in the VFIO discord server **Reports that do no include this information will be ignored and closed**
* https://discord.gg/4ahCn4c
*Issues that are not bug reports or feature requests will be closed & ignored*
### Errors that are not bugs
Some errors generated by the LG client are not bugs, but rather issues with your
system's configuration and/or timing. Please do not report these, but rather use
one of the above resources to ask for advice/help.
* `LGMP_ERR_QUEUE_UNSUBSCRIBED` - Failure to heed advice on things such as
using `isolcpus` and CPU pinning may result in this message, especially if you
are over-taxing your CPU.
* `Could not create an SDL window: *` - Failure to create a SDL window is not an
issue with Looking Glass but rather a more substantial issue with your system,
such as missing hardware support for the RGBA32 pixmap format, or missing
required OpenGL EGL features.
* `The host application is not compatible with this client` - The Looking Glass
Host application in Windows is the incorrect version and is not compatible,
you need to make sure you run matching versions of both the host and client
applications.
### Bug Report Required Information
The entire (not truncated) output from the client application (if applicable).
To obtain this run `looking-glass-client` in a terminal.
```
PASTE CLIENT OUTPUT HERE
```
The entire (not truncated) log file from the host application (if applicable).
To obtain this locate the log file on your system, it will be in one of the
following two locations depending on how you are launching the Looking Glass Host
application:
* C:\Windows\Temp\looking-glass.txt
* C:\Users\YOUR_USER\AppData\Local\Temp\looking-glass.txt
This log may be quite long, please delete the file first and then proceed to
launch the host and reproduce the issue so that the log only contains the
pertinent information.
```
PASTE HOST LOG FILE CONTENTS HERE
```
If the client is unexpetedly exiting without a backtrace, please provide one via
gdb with the command `thread apply all bt`. If you are unsure how to do this
please watch the video below on how to perform a Debug build and generate this
backtrace.
https://www.youtube.com/watch?v=EqxxJK9Yo64
```
PASTE FULL BACKTRACE HERE
```

1
.gitignore vendored
View File

@@ -7,4 +7,3 @@ module/modules.order
*.a *.a
*.o *.o
*.exe *.exe
*/build

6
.gitmodules vendored
View File

@@ -1,6 +0,0 @@
[submodule "LGMP"]
path = repos/LGMP
url = https://github.com/gnif/LGMP.git
[submodule "repos/PureSpice"]
path = repos/PureSpice
url = https://github.com/gnif/PureSpice

View File

@@ -1,20 +1,14 @@
# Looking Glass # Looking Glass
An extremely low latency KVMFR (KVM FrameRelay) implementation for guests with An extremely low latency KVMFR (KVM FrameRelay) implementation for guests with VGA PCI Passthrough.
VGA PCI Passthrough.
* Project Website: https://looking-glass.hostfission.com * Project Website: https://looking-glass.hostfission.com
* Getting Started: https://looking-glass.hostfission.com/wiki/Installation
## Donations ## Donations
I (Geoffrey McRae) am the primary developer behind this project and I have I (Geoffrey McRae) am the primary developer behind this project and I have invested thousands of hours of development time into it.
invested thousands of hours of development time into it. If you like this project and find it useful and would like to help out you can support me directly using the following platforms.
If you like this project and find it useful and would like to help out you can
support me directly using the following platforms.
* [GitHub](https://github.com/sponsors/gnif)
* [Ko-Fi](https://ko-fi.com/lookingglass) * [Ko-Fi](https://ko-fi.com/lookingglass)
* [Patreon](https://www.patreon.com/gnif) * [Patreon](https://www.patreon.com/gnif)
* [Paypal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=ESQ72XUPGKXRY) * [Paypal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=ESQ72XUPGKXRY)
@@ -22,27 +16,19 @@ support me directly using the following platforms.
## Documentation ## Documentation
** IMPORTANT **
This project contains submodules that must be checked out if building from the
git repository! If you are not a developer and just want to compile Looking
Glass please download the source archive from the website instead:
https://looking-glass.hostfission.com/downloads
Please also be sure to see the following files for more information Please also be sure to see the following files for more information
Note: The `README.md` files are slowly being deprecated from this project in
favor of the wiki at https://looking-glass.hostfission.com/wiki, and as such the
information in these files may be dated.
* [client/README.md](client/README.md) * [client/README.md](client/README.md)
* [host/README.md](host/README.md) * [c-host/README.md](c-host/README.md)
* [module/README.md](module/README.md) * [module/README.md](module/README.md)
## Obtaining and using Looking Glass
Please see https://looking-glass.hostfission.com/quickstart
## Latest Version ## Latest Version
If you would like to use the latest bleeding edge version of Looking Glass please If you would like to use the latest bleeding edge version of Looking Glass please be aware there will be no support at this time.
be aware there will be no support at this time.
Latest bleeding edge builds of the Windows host application can be obtained from: Latest bleeding edge builds of the Windows host application can be obtained from:
https://looking-glass.hostfission.com/downloads https://looking-glass.hostfission.com/downloads

View File

@@ -1 +1 @@
B2-rc4-11-g8692e9af80+1 B1-43-g83047cbc3e+1

1
_config.yml Normal file
View File

@@ -0,0 +1 @@
theme: jekyll-theme-cayman

View File

@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.0) cmake_minimum_required(VERSION 3.0)
project(profiler-client C) project(looking-glass-arbiter C)
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/") set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/")
@@ -29,38 +29,34 @@ set(EXE_FLAGS "-Wl,--gc-sections")
set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD 11)
execute_process( execute_process(
COMMAND cat ../../VERSION COMMAND cat ../VERSION
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
OUTPUT_VARIABLE BUILD_VERSION OUTPUT_VARIABLE BUILD_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_STRIP_TRAILING_WHITESPACE
) )
add_definitions(-D BUILD_VERSION='"${BUILD_VERSION}"') add_definitions(-D BUILD_VERSION='"${BUILD_VERSION}"')
get_filename_component(PROJECT_TOP "${PROJECT_SOURCE_DIR}/../.." ABSOLUTE) get_filename_component(PROJECT_TOP "${PROJECT_SOURCE_DIR}/.." ABSOLUTE)
include_directories( include_directories(
${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}/include
${CMAKE_BINARY_DIR}/include ${CMAKE_BINARY_DIR}/include
) )
link_libraries(
rt
m
)
set(SOURCES set(SOURCES
src/main.c src/main.c
) )
add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common") add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common")
add_subdirectory("${PROJECT_TOP}/repos/LGMP/lgmp" "${CMAKE_BINARY_DIR}/lgmp" ) add_subdirectory("${PROJECT_TOP}/porthole" "${CMAKE_BINARY_DIR}/porthole")
add_executable(profiler-client ${SOURCES}) add_executable(looking-glass-arbiter ${SOURCES})
target_compile_options(profiler-client PUBLIC ${PKGCONFIG_CFLAGS_OTHER}) target_link_libraries(looking-glass-arbiter
target_link_libraries(profiler-client
${EXE_FLAGS} ${EXE_FLAGS}
lg_common lg_common
lgmp porthole
) )
install(PROGRAMS ${CMAKE_BINARY_DIR}/looking-glass-arbiter DESTINATION bin/ COMPONENT binary)
feature_summary(WHAT ENABLED_FEATURES DISABLED_FEATURES) feature_summary(WHAT ENABLED_FEATURES DISABLED_FEATURES)

55
arbiter/src/main.c Normal file
View File

@@ -0,0 +1,55 @@
#include "common/debug.h"
#include "common/option.h"
#include "porthole/client.h"
static struct Option options[] =
{
// app options
{
.module = "host",
.name = "socket",
.description = "The porthole host socket",
.type = OPTION_TYPE_STRING,
.value.x_string = "/var/tmp/porthole",
},
{0}
};
static void map_event(uint32_t type, PortholeMap * map)
{
DEBUG_INFO("map_event: %u, %u, %u", type, map->id, map->size);
}
static void unmap_event(uint32_t id)
{
DEBUG_INFO("unmap_event: %u", id);
}
static void discon_event()
{
DEBUG_INFO("discon_event");
}
int main(int argc, char * argv[])
{
option_register(options);
if (!option_parse(argc, argv))
return -1;
if (!option_validate())
return -1;
PortholeClient phc;
if (!porthole_client_open(
&phc,
option_get_string("host", "socket"),
map_event,
unmap_event,
discon_event))
{
return -1;
}
porthole_client_close(&phc);
return 0;
}

View File

@@ -41,6 +41,7 @@ get_filename_component(PROJECT_TOP "${PROJECT_SOURCE_DIR}/.." ABSOLUTE)
include_directories( include_directories(
${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}/include
${CMAKE_BINARY_DIR}/include ${CMAKE_BINARY_DIR}/include
${PROJECT_TOP}/vendor/ivshmem
${PKGCONFIG_INCLUDE_DIRS} ${PKGCONFIG_INCLUDE_DIRS}
${GMP_INCLUDE_DIR} ${GMP_INCLUDE_DIR}
) )
@@ -52,8 +53,7 @@ set(SOURCES
src/app.c src/app.c
) )
add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common") add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common")
add_subdirectory("${PROJECT_TOP}/repos/LGMP/lgmp" "${CMAKE_BINARY_DIR}/lgmp" )
add_subdirectory(platform) add_subdirectory(platform)
if(WIN32) if(WIN32)
@@ -64,7 +64,6 @@ endif()
target_link_libraries(looking-glass-host target_link_libraries(looking-glass-host
lg_common lg_common
platform platform
lgmp
) )
set_target_properties(looking-glass-host PROPERTIES LINK_FLAGS "-Wl,--gc-sections") set_target_properties(looking-glass-host PROPERTIES LINK_FLAGS "-Wl,--gc-sections")
install(PROGRAMS ${CMAKE_BINARY_DIR}/looking-glass-host DESTINATION bin/ COMPONENT binary) install(PROGRAMS ${CMAKE_BINARY_DIR}/looking-glass-host DESTINATION bin/ COMPONENT binary)

View File

@@ -2,26 +2,37 @@
## What is this? ## What is this?
The Looking Glass Host application for the Guest Virtual Machine. This is a rewrite of the host application in pure C using the MinGW toolchain.
## What platforms does this support? ## Why make this?
Currently only Windows is supported however there is some initial support for Linux at this time. Several reasons:
1. The client is written in C and I would like to unify the project's language
2. The host is currently hard to build using MinGW and is very Windows specific
3. The host is a jumbled mess of code from all the experimentation going on
4. I would eventually like to be able to port this to run on Linux guests
## When will it be ready?
Soon :)
## Will it replace the C++ host?
Yes, but only when it is feature complete.
## Why doesn't this use CMake?
It does now...
~~Because win-builds doesn't distribute it, so to make it easy for everyone to compile we do not require it.~~
## How do I build it? ## How do I build it?
#### For Windows on Windows #### For Windows on Windows
1. download and install msys2 x86_64 from http://www.msys2.org/ following the setup instructions provided
3. execute `pacman -Fy` and then `pacman -Sy git make mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake`
4. run "C:\msys64\mingw64.exe"
5. checkout the project
`git clone https://github.com/gnif/LookingGlass.git`
6. configure the project and build it
``` ```
mkdir LookingGlass/host/build mkdir build
cd LookingGlass/host/build cd build
cmake -G "MSYS Makefiles" .. cmake -G "MSYS Makefiles" ..
make make
``` ```
@@ -44,46 +55,17 @@ cmake -DCMAKE_TOOLCHAIN_FILE=../toolchain-mingw64.cmake ..
make make
``` ```
## Building the Windows installer
Install NSIS compiler
Build the host program, see above sections.
Build installer with `makensis platform/Windows/installer.nsi`
The resulting installer will be at
`platform/Windows/looking-glass-host-setup.exe`
## Where is the log? ## Where is the log?
It is in your user's temp directory: It is in your user's temp directory:
%TEMP%\looking-glass-host.txt %TEMP%\looking-glass-host.txt
Or if running as a system service it will be located in: For example:
C:\Windows\Temp\looking-glass-host.txt C:\Users\YourUser\AppData\Local\Temp\looking-glass-host.txt
You can find out where the file is by right clicking on the tray icon and You can also open it by simply right clicking the tray icon and selecting "Open Log File"
selecting "Log File Location"
### High priority capture using DXGI and Secure Desktop (UAC) capture support
By default Windows gives priority to the foreground application for any GPU
work which causes issues with capture if the foreground application is consuming
100% of the available GPU resources. The looking glass host application is able
to increase the kernel GPU thread to realtime priority which fixes this, but in
order to do so it must run as the `SYSTEM` user account. To do this, Looking
Glass needs to run as a service. This can be accomplished by either using the
NSIS installer which will do this for you, or you can use the following command
to Install the service manually:
looking-glass-host.exe InstallService
To remove the service use the following command:
looking-glass-host.exe UninstallService
This will also enable the host application to capture the secure desktop which
includes things like the lock screen and UAC prompts.
## Why does this version require Administrator privileges ## Why does this version require Administrator privileges

View File

@@ -61,39 +61,31 @@ CaptureFrame;
typedef struct CapturePointer typedef struct CapturePointer
{ {
bool positionUpdate;
int x, y; int x, y;
bool visible; bool visible;
bool shapeUpdate; bool shapeUpdate;
CaptureFormat format; CaptureFormat format;
unsigned int hx, hy;
unsigned int width, height; unsigned int width, height;
unsigned int pitch; unsigned int pitch;
} }
CapturePointer; CapturePointer;
typedef bool (*CaptureGetPointerBuffer )(void ** data, uint32_t * size);
typedef void (*CapturePostPointerBuffer)(CapturePointer pointer);
typedef struct CaptureInterface typedef struct CaptureInterface
{ {
const char * (*getName )(); const char * (*getName )();
void (*initOptions )(); void (*initOptions )();
bool(*create)( bool (*create )();
CaptureGetPointerBuffer getPointerBufferFn, bool (*init )(void * pointerShape, const unsigned int pointerSize);
CapturePostPointerBuffer postPointerBufferFn
);
bool (*init )();
void (*stop )(); void (*stop )();
bool (*deinit )(); bool (*deinit )();
void (*free )(); void (*free )();
unsigned int (*getMaxFrameSize)(); unsigned int (*getMaxFrameSize)();
CaptureResult (*capture )(); CaptureResult (*capture )();
CaptureResult (*waitFrame )(CaptureFrame * frame); CaptureResult (*waitFrame )(CaptureFrame * frame );
CaptureResult (*getFrame )(FrameBuffer * frame); CaptureResult (*getFrame )(FrameBuffer frame );
CaptureResult (*getPointer)(CapturePointer * pointer);
} }
CaptureInterface; CaptureInterface;

View File

@@ -0,0 +1,54 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#pragma once
#include <stdbool.h>
int app_main(int argc, char * argv[]);
bool app_init();
void app_quit();
// these must be implemented for each OS
const char * os_getExecutable();
unsigned int os_shmemSize();
bool os_shmemMmap(void **ptr);
void os_shmemUnmap();
// os specific thread functions
typedef struct osThreadHandle osThreadHandle;
typedef int (*osThreadFunction)(void * opaque);
bool os_createThread(const char * name, osThreadFunction function, void * opaque, osThreadHandle ** handle);
bool os_joinThread (osThreadHandle * handle, int * resultCode);
// os specific event functions
#define TIMEOUT_INFINITE ((unsigned int)~0)
typedef struct osEventHandle osEventHandle;
osEventHandle * os_createEvent(bool autoReset);
void os_freeEvent (osEventHandle * handle);
bool os_waitEvent (osEventHandle * handle, unsigned int timeout);
bool os_waitEvents (osEventHandle * handles[], int count, bool waitAll, unsigned int timeout);
bool os_signalEvent(osEventHandle * handle);
bool os_resetEvent (osEventHandle * handle);

View File

@@ -20,7 +20,6 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "interface/capture.h" #include "interface/capture.h"
#include "interface/platform.h" #include "interface/platform.h"
#include "common/debug.h" #include "common/debug.h"
#include "common/event.h"
#include <string.h> #include <string.h>
#include <assert.h> #include <assert.h>
#include <stdlib.h> #include <stdlib.h>
@@ -38,7 +37,7 @@ struct xcb
uint32_t seg; uint32_t seg;
int shmID; int shmID;
void * data; void * data;
LGEvent * frameEvent; osEventHandle * frameEvent;
unsigned int width; unsigned int width;
unsigned int height; unsigned int height;
@@ -68,7 +67,7 @@ static bool xcb_create()
this = (struct xcb *)calloc(sizeof(struct xcb), 1); this = (struct xcb *)calloc(sizeof(struct xcb), 1);
this->shmID = -1; this->shmID = -1;
this->data = (void *)-1; this->data = (void *)-1;
this->frameEvent = lgCreateEvent(true, 20); this->frameEvent = os_createEvent(true);
if (!this->frameEvent) if (!this->frameEvent)
{ {
@@ -85,7 +84,7 @@ static bool xcb_init()
assert(this); assert(this);
assert(!this->initialized); assert(!this->initialized);
lgResetEvent(this->frameEvent); os_resetEvent(this->frameEvent);
this->xcb = xcb_connect(NULL, NULL); this->xcb = xcb_connect(NULL, NULL);
if (!this->xcb || xcb_connection_has_error(this->xcb)) if (!this->xcb || xcb_connection_has_error(this->xcb))
@@ -159,7 +158,7 @@ static bool xcb_deinit()
static void xcb_free() static void xcb_free()
{ {
lgFreeEvent(this->frameEvent); os_freeEvent(this->frameEvent);
free(this); free(this);
this = NULL; this = NULL;
} }
@@ -188,29 +187,20 @@ static CaptureResult xcb_capture()
0); 0);
this->hasFrame = true; this->hasFrame = true;
lgSignalEvent(this->frameEvent); os_signalEvent(this->frameEvent);
} }
return CAPTURE_RESULT_OK; return CAPTURE_RESULT_OK;
} }
static CaptureResult xcb_waitFrame(CaptureFrame * frame) static CaptureResult xcb_getFrame(CaptureFrame * frame)
{
lgWaitEvent(this->frameEvent, TIMEOUT_INFINITE);
frame->width = this->width;
frame->height = this->height;
frame->pitch = this->width * 4;
frame->stride = this->width;
frame->format = CAPTURE_FMT_BGRA;
return CAPTURE_RESULT_OK;
}
static CaptureResult xcb_getFrame(FrameBuffer frame)
{ {
assert(this); assert(this);
assert(this->initialized); assert(this->initialized);
assert(frame);
assert(frame->data);
os_waitEvent(this->frameEvent, TIMEOUT_INFINITE);
xcb_shm_get_image_reply_t * img; xcb_shm_get_image_reply_t * img;
img = xcb_shm_get_image_reply(this->xcb, this->imgC, NULL); img = xcb_shm_get_image_reply(this->xcb, this->imgC, NULL);
@@ -220,7 +210,12 @@ static CaptureResult xcb_getFrame(FrameBuffer frame)
return CAPTURE_RESULT_ERROR; return CAPTURE_RESULT_ERROR;
} }
framebuffer_write(frame, this->data, this->width * this->height * 4); frame->width = this->width;
frame->height = this->height;
frame->pitch = this->width * 4;
frame->stride = this->width;
frame->format = CAPTURE_FMT_BGRA;
memcpy(frame->data, this->data, this->width * this->height * 4);
free(img); free(img);
this->hasFrame = false; this->hasFrame = false;
@@ -242,7 +237,6 @@ struct CaptureInterface Capture_XCB =
.free = xcb_free, .free = xcb_free,
.getMaxFrameSize = xcb_getMaxFrameSize, .getMaxFrameSize = xcb_getMaxFrameSize,
.capture = xcb_capture, .capture = xcb_capture,
.waitFrame = xcb_waitFrame,
.getFrame = xcb_getFrame, .getFrame = xcb_getFrame,
.getPointer = xcb_getPointer .getPointer = xcb_getPointer
}; };

View File

@@ -0,0 +1,453 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "interface/platform.h"
#include "common/debug.h"
#include "common/option.h"
#include <assert.h>
#include <getopt.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <signal.h>
#include <errno.h>
struct app
{
const char * executable;
unsigned int shmSize;
int shmFD;
void * shmMap;
};
static struct app app;
struct osThreadHandle
{
const char * name;
osThreadFunction function;
void * opaque;
pthread_t handle;
int resultCode;
};
void sigHandler(int signo)
{
DEBUG_INFO("SIGINT");
app_quit();
}
static int uioOpenFile(const char * shmDevice, const char * file)
{
int len = snprintf(NULL, 0, "/sys/class/uio/%s/%s", shmDevice, file);
char * path = malloc(len + 1);
sprintf(path, "/sys/class/uio/%s/%s", shmDevice, file);
int fd = open(path, O_RDONLY);
if (fd < 0)
{
free(path);
return -1;
}
free(path);
return fd;
}
static char * uioGetName(const char * shmDevice)
{
int fd = uioOpenFile(shmDevice, "name");
if (fd < 0)
return NULL;
char * name = malloc(32);
int len = read(fd, name, 31);
if (len <= 0)
{
free(name);
close(fd);
return NULL;
}
name[len] = '\0';
close(fd);
while(len > 0 && name[len-1] == '\n')
{
--len;
name[len] = '\0';
}
return name;
}
static int shmOpenDev(const char * shmDevice)
{
int len = snprintf(NULL, 0, "/dev/%s", shmDevice);
char * path = malloc(len + 1);
sprintf(path, "/dev/%s", shmDevice);
int fd = open(path, O_RDWR, (mode_t)0600);
if (fd < 0)
{
DEBUG_ERROR("Failed to open: %s", path);
DEBUG_ERROR("Did you remmeber to modprobe the kvmfr module?");
free(path);
return -1;
}
free(path);
return fd;
}
static bool shmDeviceValidator(struct Option * opt, const char ** error)
{
char * name = uioGetName(opt->value.x_string);
if (!name)
{
*error = "Failed to get the uio device name";
return false;
}
if (strcmp(name, "KVMFR") != 0)
{
free(name);
*error = "Device is not a KVMFR device";
return false;
}
free(name);
return true;
}
static StringList shmDeviceGetValues(struct Option * option)
{
StringList sl = stringlist_new(true);
DIR * d = opendir("/sys/class/uio");
if (!d)
return sl;
struct dirent * dir;
while((dir = readdir(d)) != NULL)
{
if (dir->d_name[0] == '.')
continue;
char * name = uioGetName(dir->d_name);
if (!name)
continue;
if (strcmp(name, "KVMFR") == 0)
stringlist_push(sl, strdup(dir->d_name));
free(name);
}
closedir(d);
return sl;
}
int main(int argc, char * argv[])
{
app.executable = argv[0];
struct Option options[] =
{
{
.module = "os",
.name = "shmDevice",
.description = "The IVSHMEM device to use",
.type = OPTION_TYPE_STRING,
.value.x_string = "uio0",
.validator = shmDeviceValidator,
.getValues = shmDeviceGetValues
},
{0}
};
option_register(options);
int result = app_main(argc, argv);
os_shmemUnmap();
close(app.shmFD);
return result;
}
bool app_init()
{
const char * shmDevice = option_get_string("os", "shmDevice");
// get the device size
int fd = uioOpenFile(shmDevice, "maps/map0/size");
if (fd < 0)
{
DEBUG_ERROR("Failed to open %s/size", shmDevice);
DEBUG_ERROR("Did you remmeber to modprobe the kvmfr module?");
return false;
}
char size[32];
int len = read(fd, size, sizeof(size) - 1);
if (len <= 0)
{
DEBUG_ERROR("Failed to read the device size");
close(fd);
return false;
}
size[len] = '\0';
close(fd);
app.shmSize = strtoul(size, NULL, 16);
// open the device
app.shmFD = shmOpenDev(shmDevice);
app.shmMap = MAP_FAILED;
if (app.shmFD < 0)
return false;
DEBUG_INFO("KVMFR Device : %s", shmDevice);
signal(SIGINT, sigHandler);
return true;
}
const char * os_getExecutable()
{
return app.executable;
}
unsigned int os_shmemSize()
{
return app.shmSize;
}
bool os_shmemMmap(void **ptr)
{
if (app.shmMap == MAP_FAILED)
{
app.shmMap = mmap(0, app.shmSize, PROT_READ | PROT_WRITE, MAP_SHARED, app.shmFD, 0);
if (app.shmMap == MAP_FAILED)
{
const char * shmDevice = option_get_string("os", "shmDevice");
DEBUG_ERROR("Failed to map the shared memory device: %s", shmDevice);
return false;
}
}
*ptr = app.shmMap;
return true;
}
void os_shmemUnmap()
{
if (app.shmMap == MAP_FAILED)
return;
munmap(app.shmMap, app.shmSize);
app.shmMap = MAP_FAILED;
}
static void * threadWrapper(void * opaque)
{
osThreadHandle * handle = (osThreadHandle *)opaque;
handle->resultCode = handle->function(handle->opaque);
return NULL;
}
bool os_createThread(const char * name, osThreadFunction function, void * opaque, osThreadHandle ** handle)
{
*handle = (osThreadHandle*)malloc(sizeof(osThreadHandle));
(*handle)->name = name;
(*handle)->function = function;
(*handle)->opaque = opaque;
if (pthread_create(&(*handle)->handle, NULL, threadWrapper, *handle) != 0)
{
DEBUG_ERROR("pthread_create failed for thread: %s", name);
free(*handle);
*handle = NULL;
return false;
}
return true;
}
bool os_joinThread(osThreadHandle * handle, int * resultCode)
{
if (pthread_join(handle->handle, NULL) != 0)
{
DEBUG_ERROR("pthread_join failed for thread: %s", handle->name);
free(handle);
return false;
}
if (resultCode)
*resultCode = handle->resultCode;
free(handle);
return true;
}
struct osEventHandle
{
pthread_mutex_t mutex;
pthread_cond_t cond;
bool flag;
bool autoReset;
};
osEventHandle * os_createEvent(bool autoReset)
{
osEventHandle * handle = (osEventHandle *)calloc(sizeof(osEventHandle), 1);
if (!handle)
{
DEBUG_ERROR("Failed to allocate memory");
return NULL;
}
if (pthread_mutex_init(&handle->mutex, NULL) != 0)
{
DEBUG_ERROR("Failed to create the mutex");
free(handle);
return NULL;
}
if (pthread_cond_init(&handle->cond, NULL) != 0)
{
pthread_mutex_destroy(&handle->mutex);
free(handle);
return NULL;
}
handle->autoReset = autoReset;
return handle;
}
void os_freeEvent(osEventHandle * handle)
{
assert(handle);
pthread_cond_destroy (&handle->cond );
pthread_mutex_destroy(&handle->mutex);
free(handle);
}
bool os_waitEvent(osEventHandle * handle, unsigned int timeout)
{
assert(handle);
if (pthread_mutex_lock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to lock the mutex");
return false;
}
while(!handle->flag)
{
if (timeout == TIMEOUT_INFINITE)
{
if (pthread_cond_wait(&handle->cond, &handle->mutex) != 0)
{
DEBUG_ERROR("Wait to wait on the condition");
return false;
}
}
else
{
struct timespec ts;
ts.tv_sec = timeout / 1000;
ts.tv_nsec = (timeout % 1000) * 1000000;
switch(pthread_cond_timedwait(&handle->cond, &handle->mutex, &ts))
{
case ETIMEDOUT:
return false;
default:
DEBUG_ERROR("Timed wait failed");
return false;
}
}
}
if (handle->autoReset)
handle->flag = false;
if (pthread_mutex_unlock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to unlock the mutex");
return false;
}
return true;
}
bool os_signalEvent(osEventHandle * handle)
{
assert(handle);
if (pthread_mutex_lock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to lock the mutex");
return false;
}
handle->flag = true;
if (pthread_mutex_unlock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to unlock the mutex");
return false;
}
if (pthread_cond_signal(&handle->cond) != 0)
{
DEBUG_ERROR("Failed to signal the condition");
return false;
}
return true;
}
bool os_resetEvent(osEventHandle * handle)
{
assert(handle);
if (pthread_mutex_lock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to lock the mutex");
return false;
}
handle->flag = false;
if (pthread_mutex_unlock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to unlock the mutex");
return false;
}
return true;
}

View File

@@ -7,7 +7,7 @@ include_directories(
add_library(platform_Windows STATIC add_library(platform_Windows STATIC
src/platform.c src/platform.c
src/service.c src/windebug.c
src/mousehook.c src/mousehook.c
) )
@@ -24,19 +24,10 @@ target_link_libraries(platform_Windows
"${PROJECT_BINARY_DIR}/resource.o" "${PROJECT_BINARY_DIR}/resource.o"
lg_common lg_common
capture capture
setupapi
userenv
wtsapi32
psapi
) )
target_include_directories(platform_Windows target_include_directories(platform_Windows
PRIVATE PRIVATE
src src
) )
# these are for the nsis installer generator
configure_file("${PROJECT_SOURCE_DIR}/installer.nsi" "${PROJECT_BINARY_DIR}/installer.nsi" COPYONLY)
configure_file("${PROJECT_TOP}/resources/icon.ico" "${PROJECT_BINARY_DIR}/icon.ico" COPYONLY)
configure_file("${PROJECT_TOP}/VERSION" "${PROJECT_BINARY_DIR}/VERSION" COPYONLY)
configure_file("${PROJECT_TOP}/LICENSE" "${PROJECT_BINARY_DIR}/LICENSE.txt" COPYONLY)

View File

@@ -20,34 +20,16 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "interface/capture.h" #include "interface/capture.h"
#include "interface/platform.h" #include "interface/platform.h"
#include "common/debug.h" #include "common/debug.h"
#include "common/windebug.h"
#include "common/option.h" #include "common/option.h"
#include "common/locking.h" #include "windows/debug.h"
#include "common/event.h"
#include <assert.h> #include <assert.h>
#include <stdatomic.h>
#include <unistd.h>
#include <dxgi.h> #include <dxgi.h>
#include <d3d11.h> #include <d3d11.h>
#include <d3dcommon.h> #include <d3dcommon.h>
#include "dxgi_extra.h" #include "dxgi_extra.h"
typedef enum _D3DKMT_SCHEDULINGPRIORITYCLASS
{
D3DKMT_SCHEDULINGPRIORITYCLASS_IDLE,
D3DKMT_SCHEDULINGPRIORITYCLASS_BELOW_NORMAL,
D3DKMT_SCHEDULINGPRIORITYCLASS_NORMAL,
D3DKMT_SCHEDULINGPRIORITYCLASS_ABOVE_NORMAL,
D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH,
D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME
}
D3DKMT_SCHEDULINGPRIORITYCLASS;
typedef NTSTATUS WINAPI (*PD3DKMTSetProcessSchedulingPriorityClass)(HANDLE, D3DKMT_SCHEDULINGPRIORITYCLASS);
#define LOCKED(x) INTERLOCKED_SECTION(this->deviceContextLock, x)
enum TextureState enum TextureState
{ {
TEXTURE_STATE_UNUSED, TEXTURE_STATE_UNUSED,
@@ -57,39 +39,44 @@ enum TextureState
typedef struct Texture typedef struct Texture
{ {
volatile enum TextureState state; enum TextureState state;
ID3D11Texture2D * tex; ID3D11Texture2D * tex;
D3D11_MAPPED_SUBRESOURCE map; D3D11_MAPPED_SUBRESOURCE map;
osEventHandle * mapped;
osEventHandle * free;
} }
Texture; Texture;
typedef struct Pointer
{
unsigned int version;
unsigned int x, y;
unsigned int w, h;
bool visible;
unsigned int pitch;
CaptureFormat format;
}
Pointer;
// locals // locals
struct iface struct iface
{ {
bool initialized; bool initialized;
LARGE_INTEGER perfFreq;
LARGE_INTEGER frameTime;
bool stop; bool stop;
HDESK desktop;
IDXGIFactory1 * factory; IDXGIFactory1 * factory;
IDXGIAdapter1 * adapter; IDXGIAdapter1 * adapter;
IDXGIOutput * output; IDXGIOutput * output;
ID3D11Device * device; ID3D11Device * device;
ID3D11DeviceContext * deviceContext; ID3D11DeviceContext * deviceContext;
LG_Lock deviceContextLock;
bool useAcquireLock;
D3D_FEATURE_LEVEL featureLevel; D3D_FEATURE_LEVEL featureLevel;
IDXGIOutputDuplication * dup; IDXGIOutputDuplication * dup;
int maxTextures; int maxTextures;
Texture * texture; Texture * texture;
int texRIndex; int texRIndex;
int texWIndex; int texWIndex;
atomic_int texReady;
bool needsRelease; bool needsRelease;
osEventHandle * pointerEvent;
CaptureGetPointerBuffer getPointerBufferFn;
CapturePostPointerBuffer postPointerBufferFn;
LGEvent * frameEvent;
unsigned int width; unsigned int width;
unsigned int height; unsigned int height;
@@ -97,8 +84,14 @@ struct iface
unsigned int stride; unsigned int stride;
CaptureFormat format; CaptureFormat format;
int lastPointerX, lastPointerY; // pointer state
bool lastPointerVisible; Pointer lastPointer;
Pointer pointer;
// pointer shape
void * pointerShape;
unsigned int pointerSize;
unsigned int pointerUsed;
}; };
static bool dpiDone = false; static bool dpiDone = false;
@@ -141,20 +134,13 @@ static void dxgi_initOptions()
.type = OPTION_TYPE_INT, .type = OPTION_TYPE_INT,
.value.x_int = 3 .value.x_int = 3
}, },
{
.module = "dxgi",
.name = "useAcquireLock",
.description = "Enable locking around `AcquireFrame` (EXPERIMENTAL, leave enabled if you're not sure!)",
.type = OPTION_TYPE_BOOL,
.value.x_bool = true
},
{0} {0}
}; };
option_register(options); option_register(options);
} }
static bool dxgi_create(CaptureGetPointerBuffer getPointerBufferFn, CapturePostPointerBuffer postPointerBufferFn) static bool dxgi_create()
{ {
assert(!this); assert(!this);
this = calloc(sizeof(struct iface), 1); this = calloc(sizeof(struct iface), 1);
@@ -164,10 +150,10 @@ static bool dxgi_create(CaptureGetPointerBuffer getPointerBufferFn, CapturePostP
return false; return false;
} }
this->frameEvent = lgCreateEvent(true, 17); // 60Hz = 16.7ms this->pointerEvent = os_createEvent(true);
if (!this->frameEvent) if (!this->pointerEvent)
{ {
DEBUG_ERROR("failed to create the frame event"); DEBUG_ERROR("failed to create the pointer event");
free(this); free(this);
return false; return false;
} }
@@ -176,38 +162,14 @@ static bool dxgi_create(CaptureGetPointerBuffer getPointerBufferFn, CapturePostP
if (this->maxTextures <= 0) if (this->maxTextures <= 0)
this->maxTextures = 1; this->maxTextures = 1;
this->useAcquireLock = option_get_bool("dxgi", "useAcquireLock"); this->texture = calloc(sizeof(struct Texture), this->maxTextures);
this->texture = calloc(sizeof(struct Texture), this->maxTextures);
this->getPointerBufferFn = getPointerBufferFn;
this->postPointerBufferFn = postPointerBufferFn;
return true; return true;
} }
static bool dxgi_init() static bool dxgi_init(void * pointerShape, const unsigned int pointerSize)
{ {
assert(this); assert(this);
this->desktop = OpenInputDesktop(0, FALSE, GENERIC_READ);
if (!this->desktop)
DEBUG_WINERROR("Failed to open the desktop", GetLastError());
else
{
if (!SetThreadDesktop(this->desktop))
{
DEBUG_WINERROR("Failed to set thread desktop", GetLastError());
CloseDesktop(this->desktop);
this->desktop = NULL;
}
}
if (!this->desktop)
{
DEBUG_INFO("The above error(s) will prevent LG from being able to capture the secure desktop (UAC dialogs)");
DEBUG_INFO("This is not a failure, please do not report this as an issue.");
DEBUG_INFO("To fix this, install and run the Looking Glass host as a service.");
DEBUG_INFO("looking-glass-host.exe InstallService");
}
// this is required for DXGI 1.5 support to function // this is required for DXGI 1.5 support to function
if (!dpiDone) if (!dpiDone)
{ {
@@ -227,12 +189,13 @@ static bool dxgi_init()
HRESULT status; HRESULT status;
DXGI_OUTPUT_DESC outputDesc; DXGI_OUTPUT_DESC outputDesc;
this->stop = false; this->pointerShape = pointerShape;
this->texRIndex = 0; this->pointerSize = pointerSize;
this->texWIndex = 0; this->pointerUsed = 0;
atomic_store(&this->texReady, 0);
lgResetEvent(this->frameEvent); this->stop = false;
this->texRIndex = 0;
this->texWIndex = 0;
status = CreateDXGIFactory1(&IID_IDXGIFactory1, (void **)&this->factory); status = CreateDXGIFactory1(&IID_IDXGIFactory1, (void **)&this->factory);
if (FAILED(status)) if (FAILED(status))
@@ -249,12 +212,7 @@ static bool dxgi_init()
if (optAdapter) if (optAdapter)
{ {
DXGI_ADAPTER_DESC1 adapterDesc; DXGI_ADAPTER_DESC1 adapterDesc;
status = IDXGIAdapter1_GetDesc1(this->adapter, &adapterDesc); IDXGIAdapter1_GetDesc1(this->adapter, &adapterDesc);
if (FAILED(status))
{
DEBUG_WINERROR("Failed to get the device description", status);
goto fail;
}
const size_t s = (wcslen(adapterDesc.Description)+1) * 2; const size_t s = (wcslen(adapterDesc.Description)+1) * 2;
char * desc = malloc(s); char * desc = malloc(s);
@@ -316,18 +274,7 @@ static bool dxgi_init()
goto fail; goto fail;
} }
static const D3D_FEATURE_LEVEL win8[] = static const D3D_FEATURE_LEVEL featureLevels[] =
{
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
D3D_FEATURE_LEVEL_9_2,
D3D_FEATURE_LEVEL_9_1
};
static const D3D_FEATURE_LEVEL win10[] =
{ {
D3D_FEATURE_LEVEL_12_1, D3D_FEATURE_LEVEL_12_1,
D3D_FEATURE_LEVEL_12_0, D3D_FEATURE_LEVEL_12_0,
@@ -340,19 +287,6 @@ static bool dxgi_init()
D3D_FEATURE_LEVEL_9_1 D3D_FEATURE_LEVEL_9_1
}; };
const D3D_FEATURE_LEVEL * featureLevels;
unsigned int featureLevelCount;
if (IsWindows8())
{
featureLevels = win8;
featureLevelCount = sizeof(win8) / sizeof(D3D_FEATURE_LEVEL);
}
else
{
featureLevels = win10;
featureLevelCount = sizeof(win10) / sizeof(D3D_FEATURE_LEVEL);
}
IDXGIAdapter * tmp; IDXGIAdapter * tmp;
status = IDXGIAdapter1_QueryInterface(this->adapter, &IID_IDXGIAdapter, (void **)&tmp); status = IDXGIAdapter1_QueryInterface(this->adapter, &IID_IDXGIAdapter, (void **)&tmp);
if (FAILED(status)) if (FAILED(status))
@@ -366,14 +300,12 @@ static bool dxgi_init()
D3D_DRIVER_TYPE_UNKNOWN, D3D_DRIVER_TYPE_UNKNOWN,
NULL, NULL,
D3D11_CREATE_DEVICE_VIDEO_SUPPORT, D3D11_CREATE_DEVICE_VIDEO_SUPPORT,
featureLevels, featureLevelCount, featureLevels, sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL),
D3D11_SDK_VERSION, D3D11_SDK_VERSION,
&this->device, &this->device,
&this->featureLevel, &this->featureLevel,
&this->deviceContext); &this->deviceContext);
LG_LOCK_INIT(this->deviceContextLock);
IDXGIAdapter_Release(tmp); IDXGIAdapter_Release(tmp);
if (FAILED(status)) if (FAILED(status))
@@ -395,29 +327,9 @@ static bool dxgi_init()
DEBUG_INFO("Shared Sys Mem : %u MiB" , (unsigned)(adapterDesc.SharedSystemMemory / 1048576)); DEBUG_INFO("Shared Sys Mem : %u MiB" , (unsigned)(adapterDesc.SharedSystemMemory / 1048576));
DEBUG_INFO("Feature Level : 0x%x" , this->featureLevel); DEBUG_INFO("Feature Level : 0x%x" , this->featureLevel);
DEBUG_INFO("Capture Size : %u x %u", this->width, this->height); DEBUG_INFO("Capture Size : %u x %u", this->width, this->height);
DEBUG_INFO("AcquireLock : %s" , this->useAcquireLock ? "enabled" : "disabled");
// bump up our priority // bump up our priority
{ {
HMODULE gdi32 = GetModuleHandleA("GDI32");
if (gdi32)
{
PD3DKMTSetProcessSchedulingPriorityClass fn =
(PD3DKMTSetProcessSchedulingPriorityClass)GetProcAddress(gdi32, "D3DKMTSetProcessSchedulingPriorityClass");
if (fn)
{
status = fn(GetCurrentProcess(), D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME);
if (FAILED(status))
{
DEBUG_WARN("Failed to set realtime GPU priority.");
DEBUG_INFO("This is not a failure, please do not report this as an issue.");
DEBUG_INFO("To fix this, install and run the Looking Glass host as a service.");
DEBUG_INFO("looking-glass-host.exe InstallService");
}
}
}
IDXGIDevice * dxgi; IDXGIDevice * dxgi;
status = ID3D11Device_QueryInterface(this->device, &IID_IDXGIDevice, (void **)&dxgi); status = ID3D11Device_QueryInterface(this->device, &IID_IDXGIDevice, (void **)&dxgi);
if (FAILED(status)) if (FAILED(status))
@@ -430,20 +342,6 @@ static bool dxgi_init()
IDXGIDevice_Release(dxgi); IDXGIDevice_Release(dxgi);
} }
// try to reduce the latency
{
IDXGIDevice1 * dxgi;
status = ID3D11Device_QueryInterface(this->device, &IID_IDXGIDevice1, (void **)&dxgi);
if (FAILED(status))
{
DEBUG_WINERROR("failed to query DXGI interface from device", status);
goto fail;
}
IDXGIDevice1_SetMaximumFrameLatency(dxgi, 1);
IDXGIDevice1_Release(dxgi);
}
IDXGIOutput5 * output5 = NULL; IDXGIOutput5 * output5 = NULL;
status = IDXGIOutput_QueryInterface(this->output, &IID_IDXGIOutput5, (void **)&output5); status = IDXGIOutput_QueryInterface(this->output, &IID_IDXGIOutput5, (void **)&output5);
if (FAILED(status)) if (FAILED(status))
@@ -551,6 +449,23 @@ static bool dxgi_init()
DEBUG_WINERROR("Failed to create texture", status); DEBUG_WINERROR("Failed to create texture", status);
goto fail; goto fail;
} }
this->texture[i].free = os_createEvent(true);
if (!this->texture[i].free)
{
DEBUG_ERROR("Failed to create the texture free event");
goto fail;
}
// pre-signal the free events to flag as unused
os_signalEvent(this->texture[i].free);
this->texture[i].mapped = os_createEvent(false);
if (!this->texture[i].mapped)
{
DEBUG_ERROR("Failed to create the texture mapped event");
goto fail;
}
} }
// map the texture simply to get the pitch and stride // map the texture simply to get the pitch and stride
@@ -565,8 +480,6 @@ static bool dxgi_init()
this->stride = mapping.RowPitch / 4; this->stride = mapping.RowPitch / 4;
ID3D11DeviceContext_Unmap(this->deviceContext, (ID3D11Resource *)this->texture[0].tex, 0); ID3D11DeviceContext_Unmap(this->deviceContext, (ID3D11Resource *)this->texture[0].tex, 0);
QueryPerformanceFrequency(&this->perfFreq) ;
QueryPerformanceCounter (&this->frameTime);
this->initialized = true; this->initialized = true;
return true; return true;
@@ -578,6 +491,9 @@ fail:
static void dxgi_stop() static void dxgi_stop()
{ {
this->stop = true; this->stop = true;
os_signalEvent(this->texture[this->texRIndex].mapped);
os_signalEvent(this->pointerEvent);
} }
static bool dxgi_deinit() static bool dxgi_deinit()
@@ -599,6 +515,20 @@ static bool dxgi_deinit()
ID3D11Texture2D_Release(this->texture[i].tex); ID3D11Texture2D_Release(this->texture[i].tex);
this->texture[i].tex = NULL; this->texture[i].tex = NULL;
} }
if (this->texture[i].free)
{
os_signalEvent(this->texture[i].free);
os_freeEvent(this->texture[i].free);
this->texture[i].free = NULL;
}
if (this->texture[i].mapped)
{
os_signalEvent(this->texture[i].mapped);
os_freeEvent(this->texture[i].mapped);
this->texture[i].mapped = NULL;
}
} }
if (this->dup) if (this->dup)
@@ -644,14 +574,6 @@ static bool dxgi_deinit()
} }
} }
LG_LOCK_FREE(this->deviceContextLock);
if (this->desktop)
{
CloseDesktop(this->desktop);
this->desktop = NULL;
}
this->initialized = false; this->initialized = false;
return true; return true;
} }
@@ -663,6 +585,7 @@ static void dxgi_free()
if (this->initialized) if (this->initialized)
dxgi_deinit(); dxgi_deinit();
os_freeEvent(this->pointerEvent);
free(this->texture); free(this->texture);
free(this); free(this);
@@ -677,12 +600,53 @@ static unsigned int dxgi_getMaxFrameSize()
return this->height * this->pitch; return this->height * this->pitch;
} }
static CaptureResult dxgi_hResultToCaptureResult(const HRESULT status) static CaptureResult dxgi_capture()
{ {
assert(this);
assert(this->initialized);
CaptureResult result;
HRESULT status;
DXGI_OUTDUPL_FRAME_INFO frameInfo;
IDXGIResource * res;
// if the read texture is pending a mapping
for(int i = 0; i < this->maxTextures; ++i)
{
if (this->texture[i].state != TEXTURE_STATE_PENDING_MAP)
continue;
Texture * tex = &this->texture[i];
// try to map the resource, but don't wait for it
status = ID3D11DeviceContext_Map(this->deviceContext, (ID3D11Resource*)tex->tex, 0, D3D11_MAP_READ, 0x100000L, &tex->map);
if (status != DXGI_ERROR_WAS_STILL_DRAWING)
{
if (FAILED(status))
{
DEBUG_WINERROR("Failed to map the texture", status);
IDXGIResource_Release(res);
return CAPTURE_RESULT_ERROR;
}
// successful map, set the state and signal that there is a frame available
tex->state = TEXTURE_STATE_MAPPED;
os_signalEvent(tex->mapped);
}
}
// release the prior frame
result = dxgi_releaseFrame();
if (result != CAPTURE_RESULT_OK)
return result;
status = IDXGIOutputDuplication_AcquireNextFrame(this->dup, 1, &frameInfo, &res);
switch(status) switch(status)
{ {
case S_OK: case S_OK:
return CAPTURE_RESULT_OK; this->needsRelease = true;
break;
case DXGI_ERROR_WAIT_TIMEOUT: case DXGI_ERROR_WAIT_TIMEOUT:
return CAPTURE_RESULT_TIMEOUT; return CAPTURE_RESULT_TIMEOUT;
@@ -692,62 +656,27 @@ static CaptureResult dxgi_hResultToCaptureResult(const HRESULT status)
return CAPTURE_RESULT_REINIT; return CAPTURE_RESULT_REINIT;
default: default:
DEBUG_WINERROR("AcquireNextFrame failed", status);
return CAPTURE_RESULT_ERROR; return CAPTURE_RESULT_ERROR;
} }
}
static CaptureResult dxgi_capture()
{
assert(this);
assert(this->initialized);
Texture * tex;
CaptureResult result;
HRESULT status;
DXGI_OUTDUPL_FRAME_INFO frameInfo;
IDXGIResource * res;
bool copyFrame = false;
bool copyPointer = false;
ID3D11Texture2D * src;
bool postPointer = false;
CapturePointer pointer = { 0 };
void * pointerShape = NULL;
UINT pointerShapeSize = 0;
// release the prior frame
result = dxgi_releaseFrame();
if (result != CAPTURE_RESULT_OK)
return result;
if (this->useAcquireLock)
{
LOCKED({
status = IDXGIOutputDuplication_AcquireNextFrame(this->dup, 1, &frameInfo, &res);
});
}
else
status = IDXGIOutputDuplication_AcquireNextFrame(this->dup, 1000, &frameInfo, &res);
result = dxgi_hResultToCaptureResult(status);
if (result != CAPTURE_RESULT_OK)
{
if (result == CAPTURE_RESULT_ERROR)
DEBUG_WINERROR("AcquireNextFrame failed", status);
return result;
}
this->needsRelease = true;
if (frameInfo.LastPresentTime.QuadPart != 0) if (frameInfo.LastPresentTime.QuadPart != 0)
{ {
tex = &this->texture[this->texWIndex]; Texture * tex = &this->texture[this->texWIndex];
// check if the texture is free, if not skip the frame to keep up // check if the texture is free, if not skip the frame to keep up
if (tex->state == TEXTURE_STATE_UNUSED) if (!os_waitEvent(tex->free, 0))
{ {
copyFrame = true; /*
NOTE: This is only informational for when debugging, skipping frames is
OK as we are likely getting frames faster then the client can render
them (ie, vsync off in a title)
*/
//DEBUG_WARN("Frame skipped");
}
else
{
ID3D11Texture2D * src;
status = IDXGIResource_QueryInterface(res, &IID_ID3D11Texture2D, (void **)&src); status = IDXGIResource_QueryInterface(res, &IID_ID3D11Texture2D, (void **)&src);
if (FAILED(status)) if (FAILED(status))
{ {
@@ -755,146 +684,84 @@ static CaptureResult dxgi_capture()
IDXGIResource_Release(res); IDXGIResource_Release(res);
return CAPTURE_RESULT_ERROR; return CAPTURE_RESULT_ERROR;
} }
// if the texture was mapped, unmap it
if (tex->state == TEXTURE_STATE_MAPPED)
{
ID3D11DeviceContext_Unmap(this->deviceContext, (ID3D11Resource*)tex->tex, 0);
tex->map.pData = NULL;
}
// issue the copy from GPU to CPU RAM and release the src
ID3D11DeviceContext_CopyResource(this->deviceContext,
(ID3D11Resource *)tex->tex, (ID3D11Resource *)src);
ID3D11Texture2D_Release(src);
// pending map
tex->state = TEXTURE_STATE_PENDING_MAP;
// advance our write pointer
if (++this->texWIndex == this->maxTextures)
this->texWIndex = 0;
} }
} }
IDXGIResource_Release(res); IDXGIResource_Release(res);
// if the pointer shape has changed // if the pointer has moved or changed state
uint32_t bufferSize; bool signalPointer = false;
if (frameInfo.PointerShapeBufferSize > 0) if (frameInfo.LastMouseUpdateTime.QuadPart)
{ {
if(!this->getPointerBufferFn(&pointerShape, &bufferSize)) if (
DEBUG_WARN("Failed to obtain a buffer for the pointer shape"); frameInfo.PointerPosition.Position.x != this->lastPointer.x ||
else frameInfo.PointerPosition.Position.y != this->lastPointer.y ||
copyPointer = true; frameInfo.PointerPosition.Visible != this->lastPointer.visible
)
{
this->pointer.x = frameInfo.PointerPosition.Position.x;
this->pointer.y = frameInfo.PointerPosition.Position.y;
this->pointer.visible = frameInfo.PointerPosition.Visible;
signalPointer = true;
}
} }
if (copyFrame || copyPointer) // if the pointer shape has changed
if (frameInfo.PointerShapeBufferSize > 0)
{ {
DXGI_OUTDUPL_POINTER_SHAPE_INFO shapeInfo; // update the buffer
LOCKED( if (frameInfo.PointerShapeBufferSize > this->pointerSize)
DEBUG_WARN("The pointer shape is too large to fit in the buffer, ignoring the shape");
else
{ {
if (copyFrame) DXGI_OUTDUPL_POINTER_SHAPE_INFO shapeInfo;
status = IDXGIOutputDuplication_GetFramePointerShape(this->dup, this->pointerSize, this->pointerShape, &this->pointerUsed, &shapeInfo);
if (FAILED(status))
{ {
// issue the copy from GPU to CPU RAM DEBUG_WINERROR("Failed to get the new pointer shape", status);
ID3D11DeviceContext_CopyResource(this->deviceContext, return CAPTURE_RESULT_ERROR;
(ID3D11Resource *)tex->tex, (ID3D11Resource *)src);
}
if (copyPointer)
{
// grab the pointer shape
status = IDXGIOutputDuplication_GetFramePointerShape(
this->dup, bufferSize, pointerShape, &pointerShapeSize, &shapeInfo);
}
ID3D11DeviceContext_Flush(this->deviceContext);
});
if (copyFrame)
{
ID3D11Texture2D_Release(src);
// set the state, and signal
tex->state = TEXTURE_STATE_PENDING_MAP;
if (atomic_fetch_add_explicit(&this->texReady, 1, memory_order_relaxed) == 0)
lgSignalEvent(this->frameEvent);
// advance the write index
if (++this->texWIndex == this->maxTextures)
this->texWIndex = 0;
// update the last frame time
this->frameTime.QuadPart = frameInfo.LastPresentTime.QuadPart;
}
if (copyPointer)
{
result = dxgi_hResultToCaptureResult(status);
if (result != CAPTURE_RESULT_OK)
{
if (result == CAPTURE_RESULT_ERROR)
DEBUG_WINERROR("Failed to get the new pointer shape", status);
return result;
} }
switch(shapeInfo.Type) switch(shapeInfo.Type)
{ {
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR : pointer.format = CAPTURE_FMT_COLOR ; break; case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR : this->pointer.format = CAPTURE_FMT_COLOR ; break;
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: pointer.format = CAPTURE_FMT_MASKED; break; case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: this->pointer.format = CAPTURE_FMT_MASKED; break;
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME : pointer.format = CAPTURE_FMT_MONO ; break; case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME : this->pointer.format = CAPTURE_FMT_MONO ; break;
default: default:
DEBUG_ERROR("Unsupported cursor format"); DEBUG_ERROR("Unsupported cursor format");
return CAPTURE_RESULT_ERROR; return CAPTURE_RESULT_ERROR;
} }
CURSORINFO ci = { .cbSize = sizeof(CURSORINFO) }; this->pointer.w = shapeInfo.Width;
if (!GetCursorInfo(&ci)) this->pointer.h = shapeInfo.Height;
{ this->pointer.pitch = shapeInfo.Pitch;
DEBUG_WINERROR("GetCursorInfo failed", GetLastError()); ++this->pointer.version;
return CAPTURE_RESULT_ERROR; signalPointer = true;
}
if (ci.hCursor)
{
ICONINFO ii;
if (!GetIconInfo(ci.hCursor, &ii))
{
DEBUG_WINERROR("GetIconInfo failed", GetLastError());
return CAPTURE_RESULT_ERROR;
}
DeleteObject(ii.hbmMask);
DeleteObject(ii.hbmColor);
pointer.hx = ii.xHotspot;
pointer.hy = ii.yHotspot;
}
else
{
pointer.hx = 0;
pointer.hy = 0;
}
pointer.shapeUpdate = true;
pointer.width = shapeInfo.Width;
pointer.height = shapeInfo.Height;
pointer.pitch = shapeInfo.Pitch;
postPointer = true;
} }
} }
if (frameInfo.LastMouseUpdateTime.QuadPart) // signal about the pointer update
{ if (signalPointer)
/* the pointer position is only valid if the pointer is visible */ os_signalEvent(this->pointerEvent);
if (frameInfo.PointerPosition.Visible &&
(frameInfo.PointerPosition.Position.x != this->lastPointerX ||
frameInfo.PointerPosition.Position.y != this->lastPointerY))
{
pointer.positionUpdate = true;
pointer.x =
this->lastPointerX =
frameInfo.PointerPosition.Position.x;
pointer.y =
this->lastPointerY =
frameInfo.PointerPosition.Position.y;
postPointer = true;
}
if (this->lastPointerVisible != frameInfo.PointerPosition.Visible)
{
this->lastPointerVisible = frameInfo.PointerPosition.Visible;
postPointer = true;
}
}
// post back the pointer information
if (postPointer)
{
pointer.visible = this->lastPointerVisible;
this->postPointerBufferFn(pointer);
}
return CAPTURE_RESULT_OK; return CAPTURE_RESULT_OK;
} }
@@ -904,43 +771,14 @@ static CaptureResult dxgi_waitFrame(CaptureFrame * frame)
assert(this); assert(this);
assert(this->initialized); assert(this->initialized);
// NOTE: the event may be signaled when there are no frames available
if(atomic_load_explicit(&this->texReady, memory_order_acquire) == 0)
{
if (!lgWaitEvent(this->frameEvent, 1000))
return CAPTURE_RESULT_TIMEOUT;
// the count will still be zero if we are stopping
if(atomic_load_explicit(&this->texReady, memory_order_acquire) == 0)
return CAPTURE_RESULT_TIMEOUT;
}
Texture * tex = &this->texture[this->texRIndex]; Texture * tex = &this->texture[this->texRIndex];
if (!os_waitEvent(tex->mapped, 1000))
return CAPTURE_RESULT_TIMEOUT;
// try to map the resource, but don't wait for it if (this->stop)
for (int i = 0; ; ++i) return CAPTURE_RESULT_REINIT;
{
HRESULT status;
LOCKED({status = ID3D11DeviceContext_Map(this->deviceContext, (ID3D11Resource*)tex->tex, 0, D3D11_MAP_READ, 0x100000L, &tex->map);});
if (status == DXGI_ERROR_WAS_STILL_DRAWING)
{
if (i == 100)
return CAPTURE_RESULT_TIMEOUT;
usleep(1); os_resetEvent(tex->mapped);
continue;
}
if (FAILED(status))
{
DEBUG_WINERROR("Failed to map the texture", status);
return CAPTURE_RESULT_ERROR;
}
break;
}
tex->state = TEXTURE_STATE_MAPPED;
frame->width = this->width; frame->width = this->width;
frame->height = this->height; frame->height = this->height;
@@ -948,20 +786,17 @@ static CaptureResult dxgi_waitFrame(CaptureFrame * frame)
frame->stride = this->stride; frame->stride = this->stride;
frame->format = this->format; frame->format = this->format;
atomic_fetch_sub_explicit(&this->texReady, 1, memory_order_release);
return CAPTURE_RESULT_OK; return CAPTURE_RESULT_OK;
} }
static CaptureResult dxgi_getFrame(FrameBuffer * frame) static CaptureResult dxgi_getFrame(FrameBuffer frame)
{ {
assert(this); assert(this);
assert(this->initialized); assert(this->initialized);
Texture * tex = &this->texture[this->texRIndex]; Texture * tex = &this->texture[this->texRIndex];
framebuffer_write(frame, tex->map.pData, this->pitch * this->height); framebuffer_write(frame, tex->map.pData, this->pitch * this->height);
LOCKED({ID3D11DeviceContext_Unmap(this->deviceContext, (ID3D11Resource*)tex->tex, 0);}); os_signalEvent(tex->free);
tex->state = TEXTURE_STATE_UNUSED;
if (++this->texRIndex == this->maxTextures) if (++this->texRIndex == this->maxTextures)
this->texRIndex = 0; this->texRIndex = 0;
@@ -969,14 +804,41 @@ static CaptureResult dxgi_getFrame(FrameBuffer * frame)
return CAPTURE_RESULT_OK; return CAPTURE_RESULT_OK;
} }
static CaptureResult dxgi_getPointer(CapturePointer * pointer)
{
assert(this);
assert(this->initialized);
if (!os_waitEvent(this->pointerEvent, 1000))
return CAPTURE_RESULT_TIMEOUT;
if (this->stop)
return CAPTURE_RESULT_REINIT;
Pointer p;
memcpy(&p, &this->pointer, sizeof(Pointer));
pointer->x = p.x;
pointer->y = p.y;
pointer->width = p.w;
pointer->height = p.h;
pointer->pitch = p.pitch;
pointer->visible = p.visible;
pointer->format = p.format;
pointer->shapeUpdate = p.version > this->lastPointer.version;
memcpy(&this->lastPointer, &p, sizeof(Pointer));
return CAPTURE_RESULT_OK;
}
static CaptureResult dxgi_releaseFrame() static CaptureResult dxgi_releaseFrame()
{ {
assert(this); assert(this);
if (!this->needsRelease) if (!this->needsRelease)
return CAPTURE_RESULT_OK; return CAPTURE_RESULT_OK;
HRESULT status; HRESULT status = IDXGIOutputDuplication_ReleaseFrame(this->dup);
LOCKED({status = IDXGIOutputDuplication_ReleaseFrame(this->dup);});
switch(status) switch(status)
{ {
case S_OK: case S_OK:
@@ -1014,5 +876,6 @@ struct CaptureInterface Capture_DXGI =
.getMaxFrameSize = dxgi_getMaxFrameSize, .getMaxFrameSize = dxgi_getMaxFrameSize,
.capture = dxgi_capture, .capture = dxgi_capture,
.waitFrame = dxgi_waitFrame, .waitFrame = dxgi_waitFrame,
.getFrame = dxgi_getFrame .getFrame = dxgi_getFrame,
}; .getPointer = dxgi_getPointer
};

View File

@@ -1,6 +1,6 @@
/* /*
Looking Glass - KVM FrameRelay (KVMFR) Client Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com> Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under This program is free software; you can redistribute it and/or modify it under
@@ -19,12 +19,11 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "interface/capture.h" #include "interface/capture.h"
#include "interface/platform.h" #include "interface/platform.h"
#include "common/windebug.h" #include "windows/platform.h"
#include "windows/debug.h"
#include "windows/mousehook.h" #include "windows/mousehook.h"
#include "common/option.h" #include "common/option.h"
#include "common/framebuffer.h" #include "common/framebuffer.h"
#include "common/event.h"
#include "common/thread.h"
#include <assert.h> #include <assert.h>
#include <stdlib.h> #include <stdlib.h>
#include <windows.h> #include <windows.h>
@@ -37,21 +36,18 @@ struct iface
bool stop; bool stop;
NvFBCHandle nvfbc; NvFBCHandle nvfbc;
bool seperateCursor; bool seperateCursor;
CaptureGetPointerBuffer getPointerBufferFn; void * pointerShape;
CapturePostPointerBuffer postPointerBufferFn; unsigned int pointerSize;
LGThread * pointerThread;
unsigned int maxWidth, maxHeight; unsigned int maxWidth, maxHeight;
unsigned int width , height; unsigned int width , height;
uint8_t * frameBuffer; uint8_t * frameBuffer;
uint8_t * diffMap;
NvFBCFrameGrabInfo grabInfo; NvFBCFrameGrabInfo grabInfo;
LGEvent * frameEvent; osEventHandle * frameEvent;
LGEvent * cursorEvents[2]; osEventHandle * cursorEvents[2];
int mouseX, mouseY, mouseHotX, mouseHotY; int mouseX, mouseY, mouseHotX, mouseHotY;
bool mouseVisible; bool mouseVisible;
@@ -60,7 +56,6 @@ struct iface
static struct iface * this = NULL; static struct iface * this = NULL;
static void nvfbc_free(); static void nvfbc_free();
static int pointerThread(void * unused);
static void getDesktopSize(unsigned int * width, unsigned int * height) static void getDesktopSize(unsigned int * width, unsigned int * height)
{ {
@@ -80,7 +75,7 @@ static void on_mouseMove(int x, int y)
{ {
this->mouseX = x; this->mouseX = x;
this->mouseY = y; this->mouseY = y;
lgSignalEvent(this->cursorEvents[0]); os_signalEvent(this->cursorEvents[0]);
} }
static const char * nvfbc_getName() static const char * nvfbc_getName()
@@ -105,9 +100,7 @@ static void nvfbc_initOptions()
option_register(options); option_register(options);
} }
static bool nvfbc_create( static bool nvfbc_create()
CaptureGetPointerBuffer getPointerBufferFn,
CapturePostPointerBuffer postPointerBufferFn)
{ {
if (!NvFBCInit()) if (!NvFBCInit())
return false; return false;
@@ -142,7 +135,7 @@ static bool nvfbc_create(
} }
free(privData); free(privData);
this->frameEvent = lgCreateEvent(true, 17); this->frameEvent = os_createEvent(true);
if (!this->frameEvent) if (!this->frameEvent)
{ {
DEBUG_ERROR("failed to create the frame event"); DEBUG_ERROR("failed to create the frame event");
@@ -150,18 +143,20 @@ static bool nvfbc_create(
return false; return false;
} }
this->seperateCursor = option_get_bool("nvfbc", "decoupleCursor"); this->seperateCursor = option_get_bool("nvfbc", "decoupleCursor");
this->getPointerBufferFn = getPointerBufferFn;
this->postPointerBufferFn = postPointerBufferFn;
return true; return true;
} }
static bool nvfbc_init() static bool nvfbc_init(void * pointerShape, const unsigned int pointerSize)
{ {
this->stop = false; this->stop = false;
this->pointerShape = pointerShape;
this->pointerSize = pointerSize;
getDesktopSize(&this->width, &this->height); getDesktopSize(&this->width, &this->height);
lgResetEvent(this->frameEvent); os_resetEvent(this->frameEvent);
HANDLE event; HANDLE event;
if (!NvFBCToSysSetup( if (!NvFBCToSysSetup(
@@ -169,58 +164,38 @@ static bool nvfbc_init()
BUFFER_FMT_ARGB, BUFFER_FMT_ARGB,
!this->seperateCursor, !this->seperateCursor,
this->seperateCursor, this->seperateCursor,
true, false,
DIFFMAP_BLOCKSIZE_128X128, 0,
(void **)&this->frameBuffer, (void **)&this->frameBuffer,
(void **)&this->diffMap, NULL,
&event &event
)) ))
{ {
return false; return false;
} }
this->cursorEvents[0] = lgCreateEvent(true, 10); this->cursorEvents[0] = os_createEvent(true);
mouseHook_install(on_mouseMove); mouseHook_install(on_mouseMove);
if (this->seperateCursor) if (this->seperateCursor)
this->cursorEvents[1] = lgWrapEvent(event); this->cursorEvents[1] = os_wrapEvent(event);
DEBUG_INFO("Cursor mode : %s", this->seperateCursor ? "decoupled" : "integrated"); DEBUG_INFO("Cursor mode : %s", this->seperateCursor ? "decoupled" : "integrated");
Sleep(100); Sleep(100);
if (!lgCreateThread("NvFBCPointer", pointerThread, NULL, &this->pointerThread))
{
DEBUG_ERROR("Failed to create the NvFBCPointer thread");
return false;
}
return true; return true;
} }
static void nvfbc_stop() static void nvfbc_stop()
{ {
this->stop = true; this->stop = true;
lgSignalEvent(this->cursorEvents[0]); os_signalEvent(this->cursorEvents[0]);
lgSignalEvent(this->frameEvent); os_signalEvent(this->frameEvent);
if (this->pointerThread)
{
lgJoinThread(this->pointerThread, NULL);
this->pointerThread = NULL;
}
} }
static bool nvfbc_deinit() static bool nvfbc_deinit()
{ {
mouseHook_remove(); mouseHook_remove();
if (this->cursorEvents[0])
{
lgFreeEvent(this->cursorEvents[0]);
this->cursorEvents[0] = NULL;
}
return true; return true;
} }
@@ -229,7 +204,7 @@ static void nvfbc_free()
NvFBCToSysRelease(&this->nvfbc); NvFBCToSysRelease(&this->nvfbc);
if (this->frameEvent) if (this->frameEvent)
lgFreeEvent(this->frameEvent); os_freeEvent(this->frameEvent);
free(this); free(this);
this = NULL; this = NULL;
@@ -257,28 +232,14 @@ static CaptureResult nvfbc_capture()
if (result != CAPTURE_RESULT_OK) if (result != CAPTURE_RESULT_OK)
return result; return result;
bool changed = false;
const unsigned int h = (this->height + 127) / 128;
const unsigned int w = (this->width + 127) / 128;
for(unsigned int y = 0; y < h; ++y)
for(unsigned int x = 0; x < w; ++x)
if (this->diffMap[(y*w)+x])
{
changed = true;
break;
}
if (!changed)
return CAPTURE_RESULT_TIMEOUT;
memcpy(&this->grabInfo, &grabInfo, sizeof(grabInfo)); memcpy(&this->grabInfo, &grabInfo, sizeof(grabInfo));
lgSignalEvent(this->frameEvent); os_signalEvent(this->frameEvent);
return CAPTURE_RESULT_OK; return CAPTURE_RESULT_OK;
} }
static CaptureResult nvfbc_waitFrame(CaptureFrame * frame) static CaptureResult nvfbc_waitFrame(CaptureFrame * frame)
{ {
if (!lgWaitEvent(this->frameEvent, 1000)) if (!os_waitEvent(this->frameEvent, 1000))
return CAPTURE_RESULT_TIMEOUT; return CAPTURE_RESULT_TIMEOUT;
if (this->stop) if (this->stop)
@@ -309,7 +270,7 @@ static CaptureResult nvfbc_waitFrame(CaptureFrame * frame)
return CAPTURE_RESULT_OK; return CAPTURE_RESULT_OK;
} }
static CaptureResult nvfbc_getFrame(FrameBuffer * frame) static CaptureResult nvfbc_getFrame(FrameBuffer frame)
{ {
framebuffer_write( framebuffer_write(
frame, frame,
@@ -319,55 +280,32 @@ static CaptureResult nvfbc_getFrame(FrameBuffer * frame)
return CAPTURE_RESULT_OK; return CAPTURE_RESULT_OK;
} }
static int pointerThread(void * unused) static CaptureResult nvfbc_getPointer(CapturePointer * pointer)
{ {
while(!this->stop) osEventHandle * events[2];
memcpy(&events, &this->cursorEvents, sizeof(osEventHandle *) * 2);
if (!os_waitEvents(events, this->seperateCursor ? 2 : 1, false, 1000))
return CAPTURE_RESULT_TIMEOUT;
if (this->stop)
return CAPTURE_RESULT_REINIT;
CaptureResult result;
pointer->shapeUpdate = false;
if (this->seperateCursor && events[1])
{ {
LGEvent * events[2]; result = NvFBCToSysGetCursor(this->nvfbc, pointer, this->pointerShape, this->pointerSize);
memcpy(&events, &this->cursorEvents, sizeof(LGEvent *) * 2); this->mouseVisible = pointer->visible;
if (!lgWaitEvents(events, this->seperateCursor ? 2 : 1, false, 1000)) this->mouseHotX = pointer->x;
continue; this->mouseHotY = pointer->y;
if (result != CAPTURE_RESULT_OK)
if (this->stop) return result;
break;
CaptureResult result;
CapturePointer pointer = { 0 };
if (this->seperateCursor && events[1])
{
void * data;
uint32_t size;
if (!this->getPointerBufferFn(&data, &size))
{
DEBUG_WARN("failed to get a pointer buffer");
continue;
}
result = NvFBCToSysGetCursor(this->nvfbc, &pointer, data, size);
if (result != CAPTURE_RESULT_OK)
{
DEBUG_WARN("NvFBCToSysGetCursor failed");
continue;
}
this->mouseVisible = pointer.visible;
this->mouseHotX = pointer.hx;
this->mouseHotY = pointer.hy;
}
if (events[0])
{
pointer.positionUpdate = true;
pointer.visible = this->mouseVisible;
pointer.x = this->mouseX - this->mouseHotX;
pointer.y = this->mouseY - this->mouseHotY;
}
this->postPointerBufferFn(pointer);
} }
return 0; pointer->visible = this->mouseVisible;
pointer->x = this->mouseX - this->mouseHotX;
pointer->y = this->mouseY - this->mouseHotY;
return CAPTURE_RESULT_OK;
} }
struct CaptureInterface Capture_NVFBC = struct CaptureInterface Capture_NVFBC =
@@ -383,5 +321,6 @@ struct CaptureInterface Capture_NVFBC =
.getMaxFrameSize = nvfbc_getMaxFrameSize, .getMaxFrameSize = nvfbc_getMaxFrameSize,
.capture = nvfbc_capture, .capture = nvfbc_capture,
.waitFrame = nvfbc_waitFrame, .waitFrame = nvfbc_waitFrame,
.getFrame = nvfbc_getFrame .getFrame = nvfbc_getFrame,
}; .getPointer = nvfbc_getPointer
};

View File

@@ -1,6 +1,6 @@
/* /*
Looking Glass - KVM FrameRelay (KVMFR) Client Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com> Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under This program is free software; you can redistribute it and/or modify it under
@@ -18,7 +18,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
*/ */
#include "wrapper.h" #include "wrapper.h"
#include "common/windebug.h" #include "windows/debug.h"
#include <windows.h> #include <windows.h>
#include <NvFBC/nvFBCToSys.h> #include <NvFBC/nvFBCToSys.h>
@@ -288,8 +288,8 @@ CaptureResult NvFBCToSysGetCursor(NvFBCHandle handle, CapturePointer * pointer,
return CAPTURE_RESULT_ERROR; return CAPTURE_RESULT_ERROR;
} }
pointer->hx = params.dwXHotSpot; pointer->x = params.dwXHotSpot;
pointer->hy = params.dwYHotSpot; pointer->y = params.dwYHotSpot;
pointer->width = params.dwWidth; pointer->width = params.dwWidth;
pointer->height = params.dwHeight; pointer->height = params.dwHeight;
pointer->pitch = params.dwPitch; pointer->pitch = params.dwPitch;
@@ -327,4 +327,4 @@ CaptureResult NvFBCToSysGetCursor(NvFBCHandle handle, CapturePointer * pointer,
memcpy(buffer, params.pBits, params.dwBufferSize); memcpy(buffer, params.pBits, params.dwBufferSize);
return CAPTURE_RESULT_OK; return CAPTURE_RESULT_OK;
} }

View File

@@ -1,6 +1,6 @@
/* /*
Looking Glass - KVM FrameRelay (KVMFR) Client Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com> Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under This program is free software; you can redistribute it and/or modify it under
@@ -19,9 +19,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#pragma once #pragma once
#include "debug.h" #include "common/debug.h"
#include <windows.h> #include <windows.h>
#include <stdbool.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@@ -31,8 +30,6 @@ void DebugWinError(const char * file, const unsigned int line, const char * func
#define DEBUG_WINERROR(x, y) DebugWinError(STRIPPATH(__FILE__), __LINE__, __FUNCTION__, x, y) #define DEBUG_WINERROR(x, y) DebugWinError(STRIPPATH(__FILE__), __LINE__, __FUNCTION__, x, y)
bool IsWindows8();
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@@ -1,6 +1,6 @@
/* /*
Looking Glass - KVM FrameRelay (KVMFR) Client Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com> Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under This program is free software; you can redistribute it and/or modify it under
@@ -17,12 +17,7 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA Place, Suite 330, Boston, MA 02111-1307 USA
*/ */
#pragma once #include "interface/platform.h"
#include <windows.h>
#include <stdbool.h> osEventHandle * os_wrapEvent(HANDLE event);
typedef struct LGThread LGThread;
typedef int (*LGThreadFunction)(void * opaque);
bool lgCreateThread(const char * name, LGThreadFunction function, void * opaque, LGThread ** handle);
bool lgJoinThread (LGThread * handle, int * resultCode);

View File

@@ -18,7 +18,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
*/ */
#include "windows/mousehook.h" #include "windows/mousehook.h"
#include "common/windebug.h" #include "windows/debug.h"
#include "platform.h" #include "platform.h"
#include <windows.h> #include <windows.h>
@@ -29,7 +29,6 @@ struct mouseHook
bool installed; bool installed;
HHOOK hook; HHOOK hook;
MouseHookFn callback; MouseHookFn callback;
int x, y;
}; };
static struct mouseHook mouseHook = { 0 }; static struct mouseHook mouseHook = { 0 };
@@ -93,12 +92,7 @@ static LRESULT WINAPI mouseHook_hook(int nCode, WPARAM wParam, LPARAM lParam)
if (nCode == HC_ACTION && wParam == WM_MOUSEMOVE) if (nCode == HC_ACTION && wParam == WM_MOUSEMOVE)
{ {
MSLLHOOKSTRUCT *msg = (MSLLHOOKSTRUCT *)lParam; MSLLHOOKSTRUCT *msg = (MSLLHOOKSTRUCT *)lParam;
if (mouseHook.x != msg->pt.x || mouseHook.y != msg->pt.y) mouseHook.callback(msg->pt.x, msg->pt.y);
{
mouseHook.x = msg->pt.x;
mouseHook.y = msg->pt.y;
mouseHook.callback(msg->pt.x, msg->pt.y);
}
} }
return CallNextHookEx(mouseHook.hook, nCode, wParam, lParam); return CallNextHookEx(mouseHook.hook, nCode, wParam, lParam);
} }

View File

@@ -0,0 +1,556 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "platform.h"
#include "windows/platform.h"
#include "windows/mousehook.h"
#include <windows.h>
#include <setupapi.h>
#include <shellapi.h>
#include "interface/platform.h"
#include "common/debug.h"
#include "common/option.h"
#include "windows/debug.h"
#include "ivshmem.h"
#define ID_MENU_OPEN_LOG 3000
#define ID_MENU_EXIT 3001
struct AppState
{
HINSTANCE hInst;
int argc;
char ** argv;
char executable[MAX_PATH + 1];
HANDLE shmemHandle;
bool shmemOwned;
IVSHMEM_MMAP shmemMap;
HWND messageWnd;
HMENU trayMenu;
};
static struct AppState app =
{
.shmemHandle = INVALID_HANDLE_VALUE,
.shmemOwned = false,
.shmemMap = {0}
};
struct osThreadHandle
{
const char * name;
osThreadFunction function;
void * opaque;
HANDLE handle;
DWORD threadID;
int resultCode;
};
LRESULT CALLBACK DummyWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_CALL_FUNCTION:
{
struct MSG_CALL_FUNCTION * cf = (struct MSG_CALL_FUNCTION *)lParam;
return cf->fn(cf->wParam, cf->lParam);
}
case WM_TRAYICON:
{
if (lParam == WM_RBUTTONDOWN)
{
POINT curPoint;
GetCursorPos(&curPoint);
SetForegroundWindow(hwnd);
UINT clicked = TrackPopupMenu(
app.trayMenu,
TPM_RETURNCMD | TPM_NONOTIFY,
curPoint.x,
curPoint.y,
0,
hwnd,
NULL
);
if (clicked == ID_MENU_EXIT ) app_quit();
else if (clicked == ID_MENU_OPEN_LOG)
{
const char * logFile = option_get_string("os", "logFile");
if (strcmp(logFile, "stderr") == 0)
DEBUG_INFO("Ignoring request to open the logFile, logging to stderr");
else
ShellExecute(NULL, NULL, logFile, NULL, NULL, SW_SHOWNORMAL);
}
}
break;
}
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
static int appThread(void * opaque)
{
// register our TrayIcon
NOTIFYICONDATA iconData =
{
.cbSize = sizeof(NOTIFYICONDATA),
.hWnd = app.messageWnd,
.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP,
.uCallbackMessage = WM_TRAYICON,
.szTip = "Looking Glass (host)"
};
iconData.hIcon = LoadIcon(app.hInst, IDI_APPLICATION);
Shell_NotifyIcon(NIM_ADD, &iconData);
int result = app_main(app.argc, app.argv);
Shell_NotifyIcon(NIM_DELETE, &iconData);
mouseHook_remove();
SendMessage(app.messageWnd, WM_DESTROY, 0, 0);
return result;
}
LRESULT sendAppMessage(UINT Msg, WPARAM wParam, LPARAM lParam)
{
return SendMessage(app.messageWnd, Msg, wParam, lParam);
}
static BOOL WINAPI CtrlHandler(DWORD dwCtrlType)
{
if (dwCtrlType == CTRL_C_EVENT)
{
SendMessage(app.messageWnd, WM_CLOSE, 0, 0);
return TRUE;
}
return FALSE;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
int result = 0;
app.hInst = hInstance;
char tempPath[MAX_PATH+1];
GetTempPathA(sizeof(tempPath), tempPath);
int len = snprintf(NULL, 0, "%slooking-glass-host.txt", tempPath);
char * logFilePath = malloc(len + 1);
sprintf(logFilePath, "%slooking-glass-host.txt", tempPath);
struct Option options[] =
{
{
.module = "os",
.name = "shmDevice",
.description = "The IVSHMEM device to use",
.type = OPTION_TYPE_INT,
.value.x_int = 0
},
{
.module = "os",
.name = "logFile",
.description = "The log file to write to",
.type = OPTION_TYPE_STRING,
.value.x_string = logFilePath
},
{0}
};
option_register(options);
free(logFilePath);
// convert the command line to the standard argc and argv
LPWSTR * wargv = CommandLineToArgvW(GetCommandLineW(), &app.argc);
app.argv = malloc(sizeof(char *) * app.argc);
for(int i = 0; i < app.argc; ++i)
{
const size_t s = (wcslen(wargv[i])+1) * 2;
app.argv[i] = malloc(s);
wcstombs(app.argv[i], wargv[i], s);
}
LocalFree(wargv);
GetModuleFileName(NULL, app.executable, sizeof(app.executable));
// setup a handler for ctrl+c
SetConsoleCtrlHandler(CtrlHandler, TRUE);
// create a message window so that our message pump works
WNDCLASSEX wx = {};
wx.cbSize = sizeof(WNDCLASSEX);
wx.lpfnWndProc = DummyWndProc;
wx.hInstance = hInstance;
wx.lpszClassName = "DUMMY_CLASS";
wx.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wx.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
wx.hCursor = LoadCursor(NULL, IDC_ARROW);
wx.hbrBackground = (HBRUSH)COLOR_APPWORKSPACE;
if (!RegisterClassEx(&wx))
{
DEBUG_ERROR("Failed to register message window class");
result = -1;
goto finish;
}
app.messageWnd = CreateWindowEx(0, "DUMMY_CLASS", "DUMMY_NAME", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
app.trayMenu = CreatePopupMenu();
AppendMenu(app.trayMenu, MF_STRING , ID_MENU_OPEN_LOG, "Open Log File");
AppendMenu(app.trayMenu, MF_SEPARATOR, 0 , NULL );
AppendMenu(app.trayMenu, MF_STRING , ID_MENU_EXIT , "Exit" );
// create the application thread
osThreadHandle * thread;
if (!os_createThread("appThread", appThread, NULL, &thread))
{
DEBUG_ERROR("Failed to create the main application thread");
result = -1;
goto finish;
}
while(true)
{
MSG msg;
BOOL bRet = GetMessage(&msg, NULL, 0, 0);
if (bRet > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
continue;
}
else if (bRet < 0)
{
DEBUG_ERROR("Unknown error from GetMessage");
result = -1;
goto shutdown;
}
break;
}
shutdown:
DestroyMenu(app.trayMenu);
app_quit();
if (!os_joinThread(thread, &result))
{
DEBUG_ERROR("Failed to join the main application thread");
result = -1;
}
finish:
os_shmemUnmap();
if (app.shmemHandle != INVALID_HANDLE_VALUE)
CloseHandle(app.shmemHandle);
for(int i = 0; i < app.argc; ++i)
free(app.argv[i]);
free(app.argv);
return result;
}
bool app_init()
{
const int shmDevice = option_get_int ("os", "shmDevice");
const char * logFile = option_get_string("os", "logFile" );
// redirect stderr to a file
if (logFile && strcmp(logFile, "stderr") != 0)
freopen(logFile, "a", stderr);
// always flush stderr
setbuf(stderr, NULL);
HDEVINFO deviceInfoSet;
PSP_DEVICE_INTERFACE_DETAIL_DATA infData = NULL;
SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
deviceInfoSet = SetupDiGetClassDevs(NULL, NULL, NULL, DIGCF_PRESENT | DIGCF_ALLCLASSES | DIGCF_DEVICEINTERFACE);
memset(&deviceInterfaceData, 0, sizeof(SP_DEVICE_INTERFACE_DATA));
deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
if (SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &GUID_DEVINTERFACE_IVSHMEM, shmDevice, &deviceInterfaceData) == FALSE)
{
DWORD error = GetLastError();
if (error == ERROR_NO_MORE_ITEMS)
{
DEBUG_WINERROR("Unable to enumerate the device, is it attached?", error);
return false;
}
DEBUG_WINERROR("SetupDiEnumDeviceInterfaces failed", error);
return false;
}
DWORD reqSize = 0;
SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &deviceInterfaceData, NULL, 0, &reqSize, NULL);
if (!reqSize)
{
DEBUG_WINERROR("SetupDiGetDeviceInterfaceDetail", GetLastError());
return false;
}
infData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)calloc(reqSize, 1);
infData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
if (!SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &deviceInterfaceData, infData, reqSize, NULL, NULL))
{
free(infData);
DEBUG_WINERROR("SetupDiGetDeviceInterfaceDetail", GetLastError());
return false;
}
app.shmemHandle = CreateFile(infData->DevicePath, 0, 0, NULL, OPEN_EXISTING, 0, 0);
if (app.shmemHandle == INVALID_HANDLE_VALUE)
{
SetupDiDestroyDeviceInfoList(deviceInfoSet);
free(infData);
DEBUG_WINERROR("CreateFile returned INVALID_HANDLE_VALUE", GetLastError());
return false;
}
free(infData);
SetupDiDestroyDeviceInfoList(deviceInfoSet);
return true;
}
const char * os_getExecutable()
{
return app.executable;
}
unsigned int os_shmemSize()
{
IVSHMEM_SIZE size;
if (!DeviceIoControl(app.shmemHandle, IOCTL_IVSHMEM_REQUEST_SIZE, NULL, 0, &size, sizeof(IVSHMEM_SIZE), NULL, NULL))
{
DEBUG_WINERROR("DeviceIoControl Failed", GetLastError());
return 0;
}
return (unsigned int)size;
}
bool os_shmemMmap(void **ptr)
{
if (app.shmemOwned)
{
*ptr = app.shmemMap.ptr;
return true;
}
IVSHMEM_MMAP_CONFIG config =
{
.cacheMode = IVSHMEM_CACHE_WRITECOMBINED
};
memset(&app.shmemMap, 0, sizeof(IVSHMEM_MMAP));
if (!DeviceIoControl(
app.shmemHandle,
IOCTL_IVSHMEM_REQUEST_MMAP,
&config, sizeof(IVSHMEM_MMAP_CONFIG),
&app.shmemMap, sizeof(IVSHMEM_MMAP),
NULL, NULL))
{
DEBUG_WINERROR("DeviceIoControl Failed", GetLastError());
return false;
}
*ptr = app.shmemMap.ptr;
app.shmemOwned = true;
return true;
}
void os_shmemUnmap()
{
if (!app.shmemOwned)
return;
if (!DeviceIoControl(app.shmemHandle, IOCTL_IVSHMEM_RELEASE_MMAP, NULL, 0, NULL, 0, NULL, NULL))
DEBUG_WINERROR("DeviceIoControl failed", GetLastError());
else
app.shmemOwned = false;
}
static DWORD WINAPI threadWrapper(LPVOID lpParameter)
{
osThreadHandle * handle = (osThreadHandle *)lpParameter;
handle->resultCode = handle->function(handle->opaque);
return 0;
}
bool os_createThread(const char * name, osThreadFunction function, void * opaque, osThreadHandle ** handle)
{
*handle = (osThreadHandle *)malloc(sizeof(osThreadHandle));
(*handle)->name = name;
(*handle)->function = function;
(*handle)->opaque = opaque;
(*handle)->handle = CreateThread(NULL, 0, threadWrapper, *handle, 0, &(*handle)->threadID);
if (!(*handle)->handle)
{
free(*handle);
*handle = NULL;
DEBUG_WINERROR("CreateThread failed", GetLastError());
return false;
}
return true;
}
bool os_joinThread(osThreadHandle * handle, int * resultCode)
{
while(true)
{
switch(WaitForSingleObject(handle->handle, INFINITE))
{
case WAIT_OBJECT_0:
if (resultCode)
*resultCode = handle->resultCode;
CloseHandle(handle->handle);
free(handle);
return true;
case WAIT_ABANDONED:
case WAIT_TIMEOUT:
continue;
case WAIT_FAILED:
DEBUG_WINERROR("Wait for thread failed", GetLastError());
CloseHandle(handle->handle);
free(handle);
return false;
}
break;
}
DEBUG_WINERROR("Unknown failure waiting for thread", GetLastError());
return false;
}
osEventHandle * os_createEvent(bool autoReset)
{
HANDLE event = CreateEvent(NULL, autoReset ? FALSE : TRUE, FALSE, NULL);
if (!event)
{
DEBUG_WINERROR("Failed to create the event", GetLastError());
return NULL;
}
return (osEventHandle*)event;
}
osEventHandle * os_wrapEvent(HANDLE event)
{
return (osEventHandle*)event;
}
void os_freeEvent(osEventHandle * handle)
{
CloseHandle((HANDLE)handle);
}
bool os_waitEvent(osEventHandle * handle, unsigned int timeout)
{
const DWORD to = (timeout == TIMEOUT_INFINITE) ? INFINITE : (DWORD)timeout;
while(true)
{
switch(WaitForSingleObject((HANDLE)handle, to))
{
case WAIT_OBJECT_0:
return true;
case WAIT_ABANDONED:
continue;
case WAIT_TIMEOUT:
if (timeout == TIMEOUT_INFINITE)
continue;
return false;
case WAIT_FAILED:
DEBUG_WINERROR("Wait for event failed", GetLastError());
return false;
}
DEBUG_ERROR("Unknown wait event return code");
return false;
}
}
bool os_waitEvents(osEventHandle * handles[], int count, bool waitAll, unsigned int timeout)
{
const DWORD to = (timeout == TIMEOUT_INFINITE) ? INFINITE : (DWORD)timeout;
while(true)
{
DWORD result = WaitForMultipleObjects(count, (HANDLE*)handles, waitAll, to);
if (result >= WAIT_OBJECT_0 && result < count)
{
// null non signalled events from the handle list
for(int i = 0; i < count; ++i)
if (i != result && !os_waitEvent(handles[i], 0))
handles[i] = NULL;
return true;
}
if (result >= WAIT_ABANDONED_0 && result - WAIT_ABANDONED_0 < count)
continue;
switch(result)
{
case WAIT_TIMEOUT:
if (timeout == TIMEOUT_INFINITE)
continue;
return false;
case WAIT_FAILED:
DEBUG_WINERROR("Wait for event failed", GetLastError());
return false;
}
DEBUG_ERROR("Unknown wait event return code");
return false;
}
}
bool os_signalEvent(osEventHandle * handle)
{
return SetEvent((HANDLE)handle);
}
bool os_resetEvent(osEventHandle * handle)
{
return ResetEvent((HANDLE)handle);
}

View File

@@ -30,4 +30,4 @@ struct MSG_CALL_FUNCTION
LPARAM lParam; LPARAM lParam;
}; };
LRESULT sendAppMessage(UINT Msg, WPARAM wParam, LPARAM lParam); LRESULT sendAppMessage(UINT Msg, WPARAM wParam, LPARAM lParam);

View File

@@ -1,6 +1,6 @@
/* /*
Looking Glass - KVM FrameRelay (KVMFR) Client Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com> Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under This program is free software; you can redistribute it and/or modify it under
@@ -17,7 +17,7 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA Place, Suite 330, Boston, MA 02111-1307 USA
*/ */
#include "common/windebug.h" #include "windows/debug.h"
#include <stdio.h> #include <stdio.h>
void DebugWinError(const char * file, const unsigned int line, const char * function, const char * desc, HRESULT status) void DebugWinError(const char * file, const unsigned int line, const char * function, const char * desc, HRESULT status)
@@ -37,30 +37,6 @@ void DebugWinError(const char * file, const unsigned int line, const char * func
if (buffer[i] == '\n' || buffer[i] == '\r') if (buffer[i] == '\n' || buffer[i] == '\r')
buffer[i] = 0; buffer[i] = 0;
fprintf(stderr, "%12" PRId64 " [E] %20s:%-4u | %-30s | %s: 0x%08x (%s)\n", microtime(), file, line, function, desc, (int)status, buffer); fprintf(stderr, "[E] %20s:%-4u | %-30s | %s: 0x%08x (%s)\n", file, line, function, desc, (int)status, buffer);
LocalFree(buffer); LocalFree(buffer);
} }
/* credit for this function to: https://stackoverflow.com/questions/17399302/how-can-i-detect-windows-8-1-in-a-desktop-application */
inline static BOOL CompareWindowsVersion(DWORD dwMajorVersion, DWORD dwMinorVersion)
{
OSVERSIONINFOEX ver;
DWORDLONG dwlConditionMask = 0;
ZeroMemory(&ver, sizeof(OSVERSIONINFOEX));
ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
ver.dwMajorVersion = dwMajorVersion;
ver.dwMinorVersion = dwMinorVersion;
VER_SET_CONDITION(dwlConditionMask, VER_MAJORVERSION, VER_EQUAL);
VER_SET_CONDITION(dwlConditionMask, VER_MINORVERSION, VER_EQUAL);
return VerifyVersionInfo(&ver, VER_MAJORVERSION | VER_MINORVERSION, dwlConditionMask);
}
bool IsWindows8()
{
return
(CompareWindowsVersion(6, 3) == TRUE) ||
(CompareWindowsVersion(6, 2) == TRUE);
}

475
c-host/src/app.c Normal file
View File

@@ -0,0 +1,475 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "interface/platform.h"
#include "interface/capture.h"
#include "dynamic/capture.h"
#include "common/debug.h"
#include "common/option.h"
#include "common/locking.h"
#include "common/KVMFR.h"
#include "common/crash.h"
#include <stdio.h>
#include <inttypes.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define ALIGN_DN(x) ((uintptr_t)(x) & ~0x7F)
#define ALIGN_UP(x) ALIGN_DN(x + 0x7F)
#define MAX_FRAMES 2
struct app
{
unsigned int clientInstance;
KVMFRHeader * shmHeader;
uint8_t * pointerData;
unsigned int pointerDataSize;
unsigned int pointerOffset;
CaptureInterface * iface;
uint8_t * frames;
unsigned int frameSize;
FrameBuffer frame[MAX_FRAMES];
unsigned int frameOffset[MAX_FRAMES];
bool running;
bool reinit;
osThreadHandle * pointerThread;
osThreadHandle * frameThread;
};
static struct app app;
static int pointerThread(void * opaque)
{
DEBUG_INFO("Pointer thread started");
volatile KVMFRCursor * ci = &(app.shmHeader->cursor);
uint8_t flags;
bool pointerValid = false;
bool shapeValid = false;
unsigned int clientInstance = 0;
CapturePointer pointer = { 0 };
while(app.running)
{
bool resend = false;
pointer.shapeUpdate = false;
switch(app.iface->getPointer(&pointer))
{
case CAPTURE_RESULT_OK:
{
pointerValid = true;
break;
}
case CAPTURE_RESULT_REINIT:
{
app.reinit = true;
DEBUG_INFO("Pointer thread reinit");
return 0;
}
case CAPTURE_RESULT_ERROR:
{
DEBUG_ERROR("Failed to get the pointer");
return 0;
}
case CAPTURE_RESULT_TIMEOUT:
{
// if the pointer is valid and the client has restarted, send it
if (pointerValid && clientInstance != app.clientInstance)
{
resend = true;
break;
}
continue;
}
}
clientInstance = app.clientInstance;
// wait for the client to finish with the previous update
while((ci->flags & ~KVMFR_CURSOR_FLAG_UPDATE) != 0 && app.running)
usleep(1000);
flags = KVMFR_CURSOR_FLAG_UPDATE;
ci->x = pointer.x;
ci->y = pointer.y;
flags |= KVMFR_CURSOR_FLAG_POS;
if (pointer.visible)
flags |= KVMFR_CURSOR_FLAG_VISIBLE;
// if we have shape data
if (pointer.shapeUpdate || (shapeValid && resend))
{
switch(pointer.format)
{
case CAPTURE_FMT_COLOR : ci->type = CURSOR_TYPE_COLOR ; break;
case CAPTURE_FMT_MONO : ci->type = CURSOR_TYPE_MONOCHROME ; break;
case CAPTURE_FMT_MASKED: ci->type = CURSOR_TYPE_MASKED_COLOR; break;
default:
DEBUG_ERROR("Invalid pointer format: %d", pointer.format);
continue;
}
ci->width = pointer.width;
ci->height = pointer.height;
ci->pitch = pointer.pitch;
ci->dataPos = app.pointerOffset;
++ci->version;
shapeValid = true;
flags |= KVMFR_CURSOR_FLAG_SHAPE;
}
// update the flags for the client
ci->flags = flags;
}
DEBUG_INFO("Pointer thread stopped");
return 0;
}
static int frameThread(void * opaque)
{
DEBUG_INFO("Frame thread started");
volatile KVMFRFrame * fi = &(app.shmHeader->frame);
bool frameValid = false;
int frameIndex = 0;
unsigned int clientInstance = 0;
CaptureFrame frame = { 0 };
while(app.running)
{
switch(app.iface->waitFrame(&frame))
{
case CAPTURE_RESULT_OK:
break;
case CAPTURE_RESULT_REINIT:
{
app.reinit = true;
DEBUG_INFO("Frame thread reinit");
return 0;
}
case CAPTURE_RESULT_ERROR:
{
DEBUG_ERROR("Failed to get the frame");
return 0;
}
case CAPTURE_RESULT_TIMEOUT:
{
if (frameValid && clientInstance != app.clientInstance)
{
// resend the last frame
if (--frameIndex < 0)
frameIndex = MAX_FRAMES - 1;
break;
}
continue;
}
}
clientInstance = app.clientInstance;
// wait for the client to finish with the previous frame
while(fi->flags & KVMFR_FRAME_FLAG_UPDATE && app.running)
usleep(1000);
switch(frame.format)
{
case CAPTURE_FMT_BGRA : fi->type = FRAME_TYPE_BGRA ; break;
case CAPTURE_FMT_RGBA : fi->type = FRAME_TYPE_RGBA ; break;
case CAPTURE_FMT_RGBA10: fi->type = FRAME_TYPE_RGBA10; break;
case CAPTURE_FMT_YUV420: fi->type = FRAME_TYPE_YUV420; break;
default:
DEBUG_ERROR("Unsupported frame format %d, skipping frame", frame.format);
continue;
}
fi->width = frame.width;
fi->height = frame.height;
fi->stride = frame.stride;
fi->pitch = frame.pitch;
fi->dataPos = app.frameOffset[frameIndex];
frameValid = true;
framebuffer_prepare(app.frame[frameIndex]);
INTERLOCKED_OR8(&fi->flags, KVMFR_FRAME_FLAG_UPDATE);
app.iface->getFrame(app.frame[frameIndex]);
if (++frameIndex == MAX_FRAMES)
frameIndex = 0;
}
DEBUG_INFO("Frame thread stopped");
return 0;
}
bool startThreads()
{
app.running = true;
if (!os_createThread("CursorThread", pointerThread, NULL, &app.pointerThread))
{
DEBUG_ERROR("Failed to create the pointer thread");
return false;
}
if (!os_createThread("FrameThread", frameThread, NULL, &app.frameThread))
{
DEBUG_ERROR("Failed to create the frame thread");
return false;
}
return true;
}
bool stopThreads()
{
bool ok = true;
app.running = false;
app.iface->stop();
if (app.frameThread && !os_joinThread(app.frameThread, NULL))
{
DEBUG_WARN("Failed to join the frame thread");
ok = false;
}
app.frameThread = NULL;
if (app.pointerThread && !os_joinThread(app.pointerThread, NULL))
{
DEBUG_WARN("Failed to join the pointer thread");
ok = false;
}
app.pointerThread = NULL;
return ok;
}
static bool captureStart()
{
DEBUG_INFO("Using : %s", app.iface->getName());
const unsigned int maxFrameSize = app.iface->getMaxFrameSize();
if (maxFrameSize > app.frameSize)
{
DEBUG_ERROR("Maximum frame size of %d bytes excceds maximum space available", maxFrameSize);
return false;
}
DEBUG_INFO("Capture Size : %u MiB (%u)", maxFrameSize / 1048576, maxFrameSize);
DEBUG_INFO("==== [ Capture Start ] ====");
return startThreads();
}
static bool captureRestart()
{
DEBUG_INFO("==== [ Capture Restart ] ====");
if (!stopThreads())
return false;
if (!app.iface->deinit() || !app.iface->init(app.pointerData, app.pointerDataSize))
{
DEBUG_ERROR("Failed to reinitialize the capture device");
return false;
}
if (!captureStart())
return false;
return true;
}
// this is called from the platform specific startup routine
int app_main(int argc, char * argv[])
{
if (!installCrashHandler(os_getExecutable()))
DEBUG_WARN("Failed to install the crash handler");
// register capture interface options
for(int i = 0; CaptureInterfaces[i]; ++i)
if (CaptureInterfaces[i]->initOptions)
CaptureInterfaces[i]->initOptions();
// try load values from a config file
option_load("looking-glass-host.ini");
// parse the command line arguments
if (!option_parse(argc, argv))
{
option_free();
DEBUG_ERROR("Failure to parse the command line");
return -1;
}
if (!option_validate())
{
option_free();
return -1;
}
// perform platform specific initialization
if (!app_init())
return -1;
unsigned int shmemSize = os_shmemSize();
uint8_t * shmemMap = NULL;
int exitcode = 0;
DEBUG_INFO("Looking Glass Host (" BUILD_VERSION ")");
DEBUG_INFO("IVSHMEM Size : %u MiB", shmemSize / 1048576);
if (!os_shmemMmap((void **)&shmemMap) || !shmemMap)
{
DEBUG_ERROR("Failed to map the shared memory");
return -1;
}
DEBUG_INFO("IVSHMEM Address : 0x%" PRIXPTR, (uintptr_t)shmemMap);
app.shmHeader = (KVMFRHeader *)shmemMap;
app.pointerData = (uint8_t *)ALIGN_UP(shmemMap + sizeof(KVMFRHeader));
app.pointerDataSize = 1048576; // 1MB fixed for pointer size, should be more then enough
app.pointerOffset = app.pointerData - shmemMap;
app.frames = (uint8_t *)ALIGN_UP(app.pointerData + app.pointerDataSize);
app.frameSize = ALIGN_DN((shmemSize - (app.frames - shmemMap)) / MAX_FRAMES);
DEBUG_INFO("Max Cursor Size : %u MiB", app.pointerDataSize / 1048576);
DEBUG_INFO("Max Frame Size : %u MiB", app.frameSize / 1048576);
DEBUG_INFO("Cursor : 0x%" PRIXPTR " (0x%08x)", (uintptr_t)app.pointerData, app.pointerOffset);
for (int i = 0; i < MAX_FRAMES; ++i)
{
app.frame [i] = (FrameBuffer)(app.frames + i * app.frameSize);
app.frameOffset[i] = (uint8_t *)app.frame[i] - shmemMap;
DEBUG_INFO("Frame %d : 0x%" PRIXPTR " (0x%08x)", i, (uintptr_t)app.frame[i], app.frameOffset[i]);
}
CaptureInterface * iface = NULL;
for(int i = 0; CaptureInterfaces[i]; ++i)
{
iface = CaptureInterfaces[i];
DEBUG_INFO("Trying : %s", iface->getName());
if (!iface->create())
{
iface = NULL;
continue;
}
if (iface->init(app.pointerData, app.pointerDataSize))
break;
iface->free();
iface = NULL;
}
if (!iface)
{
DEBUG_ERROR("Failed to find a supported capture interface");
exitcode = -1;
goto fail;
}
app.iface = iface;
// initialize the shared memory headers
memcpy(app.shmHeader->magic, KVMFR_HEADER_MAGIC, sizeof(KVMFR_HEADER_MAGIC));
app.shmHeader->version = KVMFR_HEADER_VERSION;
// zero and notify the client we are starting
memset(&(app.shmHeader->frame ), 0, sizeof(KVMFRFrame ));
memset(&(app.shmHeader->cursor), 0, sizeof(KVMFRCursor));
app.shmHeader->flags &= ~KVMFR_HEADER_FLAG_RESTART;
if (!captureStart())
{
exitcode = -1;
goto exit;
}
volatile char * flags = (volatile char *)&(app.shmHeader->flags);
while(app.running)
{
if (INTERLOCKED_AND8(flags, ~(KVMFR_HEADER_FLAG_RESTART)) & KVMFR_HEADER_FLAG_RESTART)
{
DEBUG_INFO("Client restarted");
++app.clientInstance;
}
if (app.reinit && !captureRestart())
{
exitcode = -1;
goto exit;
}
app.reinit = false;
switch(iface->capture())
{
case CAPTURE_RESULT_OK:
break;
case CAPTURE_RESULT_TIMEOUT:
continue;
case CAPTURE_RESULT_REINIT:
if (!captureRestart())
{
exitcode = -1;
goto exit;
}
app.reinit = false;
continue;
case CAPTURE_RESULT_ERROR:
DEBUG_ERROR("Capture interface reported a fatal error");
exitcode = -1;
goto finish;
}
}
finish:
stopThreads();
exit:
iface->deinit();
iface->free();
fail:
os_shmemUnmap();
return exitcode;
}
void app_quit()
{
app.running = false;
}

3
client/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
bin/
build/
*.swp

10
client/.vimrc Normal file
View File

@@ -0,0 +1,10 @@
packadd termdebug
function Debug()
!cd build && make
if v:shell_error == 0
TermdebugCommand build/looking-glass-client
endif
endfunction
command Debug call Debug()

View File

@@ -46,10 +46,6 @@ pkg_check_modules(PKGCONFIG REQUIRED
x11 x11
) )
pkg_check_modules(PKGCONFIG_OPT
xi
)
execute_process( execute_process(
COMMAND cat ../VERSION COMMAND cat ../VERSION
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
@@ -67,14 +63,13 @@ get_filename_component(PROJECT_TOP "${PROJECT_SOURCE_DIR}/.." ABSOLUTE)
include_directories( include_directories(
${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}/include
${CMAKE_BINARY_DIR}/include ${CMAKE_BINARY_DIR}/include
${PKGCONFIG_INCLUDE_DIRS} ${PKGCONFIG_OPT_INCLUDE_DIRS} ${PKGCONFIG_INCLUDE_DIRS}
${GMP_INCLUDE_DIR} ${GMP_INCLUDE_DIR}
) )
link_libraries( link_libraries(
${PKGCONFIG_LIBRARIES} ${PKGCONFIG_OPT_LIBRARIES} ${PKGCONFIG_LIBRARIES}
${GMP_LIBRARIES} ${GMP_LIBRARIES}
${CMAKE_DL_LIBS}
rt rt
m m
) )
@@ -88,22 +83,19 @@ set(SOURCES
src/utils.c src/utils.c
) )
add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common" ) add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common")
add_subdirectory("${PROJECT_TOP}/repos/LGMP/lgmp" "${CMAKE_BINARY_DIR}/LGMP" ) add_subdirectory(spice)
add_subdirectory("${PROJECT_TOP}/repos/PureSpice" "${CMAKE_BINARY_DIR}/PureSpice")
add_subdirectory(renderers) add_subdirectory(renderers)
add_subdirectory(clipboards) add_subdirectory(clipboards)
add_subdirectory(fonts) add_subdirectory(fonts)
add_subdirectory(decoders) add_subdirectory(decoders)
add_executable(looking-glass-client ${SOURCES}) add_executable(looking-glass-client ${SOURCES})
target_compile_options(looking-glass-client PUBLIC ${PKGCONFIG_CFLAGS_OTHER} ${PKGCONFIG_OPT_CFLAGS_OTHER}) target_compile_options(looking-glass-client PUBLIC ${PKGCONFIG_CFLAGS_OTHER})
target_link_libraries(looking-glass-client target_link_libraries(looking-glass-client
${EXE_FLAGS} ${EXE_FLAGS}
lg_common lg_common
lgmp spice
purespice
renderers renderers
clipboards clipboards
fonts fonts

View File

@@ -46,7 +46,6 @@ Below are a list of current key bindings:
| <kbd>ScrLk</kbd>+<kbd>F</kbd> | Full Screen toggle | | <kbd>ScrLk</kbd>+<kbd>F</kbd> | Full Screen toggle |
| <kbd>ScrLk</kbd>+<kbd>I</kbd> | Spice keyboard & mouse enable toggle | | <kbd>ScrLk</kbd>+<kbd>I</kbd> | Spice keyboard & mouse enable toggle |
| <kbd>ScrLk</kbd>+<kbd>N</kbd> | Toggle night vision mode (EGL renderer only!) | | <kbd>ScrLk</kbd>+<kbd>N</kbd> | Toggle night vision mode (EGL renderer only!) |
| <kbd>ScrLk</kbd>+<kbd>Q</kbd> | Quit |
| <kbd>ScrLk</kbd>+<kbd>Insert</kbd> | Increase mouse sensitivity (in capture mode only) | | <kbd>ScrLk</kbd>+<kbd>Insert</kbd> | Increase mouse sensitivity (in capture mode only) |
| <kbd>ScrLk</kbd>+<kbd>Del</kbd> | Decrease mouse sensitivity (in capture mode only) | | <kbd>ScrLk</kbd>+<kbd>Del</kbd> | Decrease mouse sensitivity (in capture mode only) |
| <kbd>ScrLk</kbd>+<kbd>F1</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F1</kbd> to the guest | | <kbd>ScrLk</kbd>+<kbd>F1</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F1</kbd> to the guest |
@@ -142,7 +141,6 @@ Command line arguments will override any options loaded from the config files.
| spice:clipboardToVM | | yes | Allow the clipboard to be syncronized TO the VM | | spice:clipboardToVM | | yes | Allow the clipboard to be syncronized TO the VM |
| spice:clipboardToLocal | | yes | Allow the clipboard to be syncronized FROM the VM | | spice:clipboardToLocal | | yes | Allow the clipboard to be syncronized FROM the VM |
| spice:scaleCursor | -j | yes | Scale cursor input position to screen size when up/down scaled | | spice:scaleCursor | -j | yes | Scale cursor input position to screen size when up/down scaled |
| spice:captureOnStart | | no | Capture mouse and keyboard on start |
|------------------------------------------------------------------------------------------------------------------| |------------------------------------------------------------------------------------------------------------------|
|--------------------------------------------------------------------------| |--------------------------------------------------------------------------|

View File

@@ -33,7 +33,6 @@ Place, Suite 330, Boston, MA 02111-1307 USA
(x)->create && \ (x)->create && \
(x)->initialize && \ (x)->initialize && \
(x)->deinitialize && \ (x)->deinitialize && \
(x)->on_restart && \
(x)->on_resize && \ (x)->on_resize && \
(x)->on_mouse_shape && \ (x)->on_mouse_shape && \
(x)->on_mouse_event && \ (x)->on_mouse_event && \
@@ -88,11 +87,10 @@ typedef void (* LG_RendererSetup)();
typedef bool (* LG_RendererCreate )(void ** opaque, const LG_RendererParams params); typedef bool (* LG_RendererCreate )(void ** opaque, const LG_RendererParams params);
typedef bool (* LG_RendererInitialize )(void * opaque, Uint32 * sdlFlags); typedef bool (* LG_RendererInitialize )(void * opaque, Uint32 * sdlFlags);
typedef void (* LG_RendererDeInitialize)(void * opaque); typedef void (* LG_RendererDeInitialize)(void * opaque);
typedef void (* LG_RendererOnRestart )(void * opaque);
typedef void (* LG_RendererOnResize )(void * opaque, const int width, const int height, const LG_RendererRect destRect); typedef void (* LG_RendererOnResize )(void * opaque, const int width, const int height, const LG_RendererRect destRect);
typedef bool (* LG_RendererOnMouseShape)(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data); typedef bool (* LG_RendererOnMouseShape)(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data);
typedef bool (* LG_RendererOnMouseEvent)(void * opaque, const bool visible , const int x, const int y); typedef bool (* LG_RendererOnMouseEvent)(void * opaque, const bool visible , const int x, const int y);
typedef bool (* LG_RendererOnFrameEvent)(void * opaque, const LG_RendererFormat format, const FrameBuffer * frame); typedef bool (* LG_RendererOnFrameEvent)(void * opaque, const LG_RendererFormat format, const FrameBuffer frame);
typedef void (* LG_RendererOnAlert )(void * opaque, const LG_MsgAlert alert, const char * message, bool ** closeFlag); typedef void (* LG_RendererOnAlert )(void * opaque, const LG_MsgAlert alert, const char * message, bool ** closeFlag);
typedef bool (* LG_RendererRender )(void * opaque, SDL_Window *window); typedef bool (* LG_RendererRender )(void * opaque, SDL_Window *window);
typedef void (* LG_RendererUpdateFPS )(void * opaque, const float avgUPS, const float avgFPS); typedef void (* LG_RendererUpdateFPS )(void * opaque, const float avgUPS, const float avgFPS);
@@ -105,7 +103,6 @@ typedef struct LG_Renderer
LG_RendererCreate create; LG_RendererCreate create;
LG_RendererInitialize initialize; LG_RendererInitialize initialize;
LG_RendererDeInitialize deinitialize; LG_RendererDeInitialize deinitialize;
LG_RendererOnRestart on_restart;
LG_RendererOnResize on_resize; LG_RendererOnResize on_resize;
LG_RendererOnMouseShape on_mouse_shape; LG_RendererOnMouseShape on_mouse_shape;
LG_RendererOnMouseEvent on_mouse_event; LG_RendererOnMouseEvent on_mouse_event;
@@ -115,4 +112,4 @@ typedef struct LG_Renderer
LG_RendererRender render; LG_RendererRender render;
LG_RendererUpdateFPS update_fps; LG_RendererUpdateFPS update_fps;
} }
LG_Renderer; LG_Renderer;

View File

@@ -19,9 +19,81 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#pragma once #pragma once
#include <stdlib.h> #include <time.h>
#include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
static inline uint64_t microtime()
{
struct timespec time;
clock_gettime(CLOCK_MONOTONIC_RAW, &time);
return ((uint64_t)time.tv_sec * 1000000) + (time.tv_nsec / 1000);
}
static inline uint64_t nanotime()
{
struct timespec time;
clock_gettime(CLOCK_MONOTONIC_RAW, &time);
return ((uint64_t)time.tv_sec * 1e9) + time.tv_nsec;
}
static inline void nsleep(uint64_t ns)
{
const struct timespec ts =
{
.tv_sec = ns / 1e9,
.tv_nsec = ns - ((ns / 1e9) * 1e9)
};
nanosleep(&ts, NULL);
}
#ifdef ATOMIC_LOCKING
#define LG_LOCK_MODE "Atomic"
typedef volatile int LG_Lock;
#define LG_LOCK_INIT(x) (x) = 0
#define LG_LOCK(x) while(__sync_lock_test_and_set(&(x), 1)) {nsleep(100);}
#define LG_UNLOCK(x) __sync_lock_release(&x)
#define LG_LOCK_FREE(x)
#else
#include <SDL2/SDL.h>
#define LG_LOCK_MODE "Mutex"
typedef SDL_mutex * LG_Lock;
#define LG_LOCK_INIT(x) (x = SDL_CreateMutex())
#define LG_LOCK(x) SDL_LockMutex(x)
#define LG_UNLOCK(x) SDL_UnlockMutex(x)
#define LG_LOCK_FREE(x) SDL_DestroyMutex(x)
#endif
static inline uint32_t get_bit(const uint8_t * const base, size_t * const offset)
{
uint32_t out = ((*(base + (*offset >> 0x3))) >> (0x7 - (*offset & 0x7))) & 0x1;
++*offset;
return out;
}
static inline uint32_t get_bits(const uint8_t * const base, size_t * const offset, const uint8_t bits)
{
uint32_t value = 0;
for (int i = 0; i < bits; ++i)
value |= (get_bit(base, offset) ? 1 : 0) << (bits - i - 1);
return value;
}
static inline uint32_t decode_u_golomb(const uint8_t * const base, size_t * const offset)
{
uint32_t i = 0;
while(get_bit(base, offset) == 0)
++i;
return ((1 << i) - 1 + get_bits(base, offset, i));
}
static inline int32_t decode_s_golomb(const uint8_t * const base, size_t * const offset)
{
const uint32_t g = decode_u_golomb(base, offset);
return (g & 0x1) ? (g + 1) / 2 : -(g / 2);
}
// reads the specified file into a new buffer // reads the specified file into a new buffer
// the callee must free the buffer // the callee must free the buffer
bool file_get_contents(const char * filename, char ** buffer, size_t * length); bool file_get_contents(const char * filename, char ** buffer, size_t * length);

View File

@@ -19,7 +19,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "alert.h" #include "alert.h"
#include "common/debug.h" #include "common/debug.h"
#include "common/locking.h" #include "utils.h"
#include "texture.h" #include "texture.h"
#include "shader.h" #include "shader.h"
@@ -215,4 +215,4 @@ void egl_alert_render(EGL_Alert * alert, const float scaleX, const float scaleY)
egl_model_render(alert->model); egl_model_render(alert->model);
glDisable(GL_BLEND); glDisable(GL_BLEND);
} }

View File

@@ -19,7 +19,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "cursor.h" #include "cursor.h"
#include "common/debug.h" #include "common/debug.h"
#include "common/locking.h" #include "utils.h"
#include "texture.h" #include "texture.h"
#include "shader.h" #include "shader.h"
@@ -286,4 +286,4 @@ void egl_cursor_render(EGL_Cursor * cursor)
} }
} }
glDisable(GL_BLEND); glDisable(GL_BLEND);
} }

View File

@@ -20,7 +20,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "desktop.h" #include "desktop.h"
#include "common/debug.h" #include "common/debug.h"
#include "common/option.h" #include "common/option.h"
#include "common/locking.h" #include "utils.h"
#include "texture.h" #include "texture.h"
#include "shader.h" #include "shader.h"
@@ -47,19 +47,22 @@ struct DesktopShader
struct EGL_Desktop struct EGL_Desktop
{ {
void * egl;
EGL_Texture * texture; EGL_Texture * texture;
struct DesktopShader * shader; // the active shader struct DesktopShader * shader; // the active shader
EGL_Model * model; EGL_Model * model;
// internals
int width, height;
// shader instances // shader instances
struct DesktopShader shader_generic; struct DesktopShader shader_generic;
struct DesktopShader shader_yuv; struct DesktopShader shader_yuv;
// internals
LG_Lock updateLock;
enum EGL_PixelFormat pixFmt;
unsigned int width, height;
unsigned int pitch;
FrameBuffer frame;
bool update;
// night vision // night vision
KeybindHandle kbNV; KeybindHandle kbNV;
int nvMax; int nvMax;
@@ -94,7 +97,7 @@ static bool egl_init_desktop_shader(
return true; return true;
} }
bool egl_desktop_init(void * egl, EGL_Desktop ** desktop) bool egl_desktop_init(EGL_Desktop ** desktop)
{ {
*desktop = (EGL_Desktop *)malloc(sizeof(EGL_Desktop)); *desktop = (EGL_Desktop *)malloc(sizeof(EGL_Desktop));
if (!*desktop) if (!*desktop)
@@ -138,7 +141,8 @@ bool egl_desktop_init(void * egl, EGL_Desktop ** desktop)
egl_model_set_default((*desktop)->model); egl_model_set_default((*desktop)->model);
egl_model_set_texture((*desktop)->model, (*desktop)->texture); egl_model_set_texture((*desktop)->model, (*desktop)->texture);
(*desktop)->egl = egl; LG_LOCK_INIT((*desktop)->updateLock);
(*desktop)->kbNV = app_register_keybind(SDL_SCANCODE_N, egl_desktop_toggle_nv, *desktop); (*desktop)->kbNV = app_register_keybind(SDL_SCANCODE_N, egl_desktop_toggle_nv, *desktop);
(*desktop)->nvMax = option_get_int("egl", "nvGainMax"); (*desktop)->nvMax = option_get_int("egl", "nvGainMax");
@@ -164,6 +168,8 @@ void egl_desktop_free(EGL_Desktop ** desktop)
if (!*desktop) if (!*desktop)
return; return;
LG_LOCK_FREE((*desktop)->updateLock);
egl_texture_free(&(*desktop)->texture ); egl_texture_free(&(*desktop)->texture );
egl_shader_free (&(*desktop)->shader_generic.shader); egl_shader_free (&(*desktop)->shader_generic.shader);
egl_shader_free (&(*desktop)->shader_yuv.shader ); egl_shader_free (&(*desktop)->shader_yuv.shader );
@@ -175,66 +181,80 @@ void egl_desktop_free(EGL_Desktop ** desktop)
*desktop = NULL; *desktop = NULL;
} }
bool egl_desktop_update(EGL_Desktop * desktop, const bool sourceChanged, const LG_RendererFormat format, const FrameBuffer * frame) bool egl_desktop_prepare_update(EGL_Desktop * desktop, const bool sourceChanged, const LG_RendererFormat format, const FrameBuffer frame)
{ {
if (sourceChanged) if (sourceChanged)
{ {
enum EGL_PixelFormat pixFmt; LG_LOCK(desktop->updateLock);
switch(format.type) switch(format.type)
{ {
case FRAME_TYPE_BGRA: case FRAME_TYPE_BGRA:
pixFmt = EGL_PF_BGRA; desktop->pixFmt = EGL_PF_BGRA;
desktop->shader = &desktop->shader_generic; desktop->shader = &desktop->shader_generic;
break; break;
case FRAME_TYPE_RGBA: case FRAME_TYPE_RGBA:
pixFmt = EGL_PF_RGBA; desktop->pixFmt = EGL_PF_RGBA;
desktop->shader = &desktop->shader_generic; desktop->shader = &desktop->shader_generic;
break; break;
case FRAME_TYPE_RGBA10: case FRAME_TYPE_RGBA10:
pixFmt = EGL_PF_RGBA10; desktop->pixFmt = EGL_PF_RGBA10;
desktop->shader = &desktop->shader_generic; desktop->shader = &desktop->shader_generic;
break; break;
case FRAME_TYPE_YUV420: case FRAME_TYPE_YUV420:
pixFmt = EGL_PF_YUV420; desktop->pixFmt = EGL_PF_YUV420;
desktop->shader = &desktop->shader_yuv; desktop->shader = &desktop->shader_yuv;
break; break;
default: default:
DEBUG_ERROR("Unsupported frame format"); DEBUG_ERROR("Unsupported frame format");
LG_UNLOCK(desktop->updateLock);
return false; return false;
} }
desktop->width = format.width; desktop->width = format.width;
desktop->height = format.height; desktop->height = format.height;
desktop->pitch = format.pitch;
desktop->frame = frame;
desktop->update = true;
/* defer the actual update as the format has changed and we need to issue GL commands first */
LG_UNLOCK(desktop->updateLock);
return true;
}
/* update the texture now */
return egl_texture_update_from_frame(desktop->texture, frame);
}
void egl_desktop_perform_update(EGL_Desktop * desktop, const bool sourceChanged)
{
if (sourceChanged)
{
LG_LOCK(desktop->updateLock);
if (!egl_texture_setup( if (!egl_texture_setup(
desktop->texture, desktop->texture,
pixFmt, desktop->pixFmt,
format.width, desktop->width,
format.height, desktop->height,
format.pitch, desktop->pitch,
true // streaming texture true // streaming texture
)) ))
{ {
DEBUG_ERROR("Failed to setup the desktop texture"); DEBUG_ERROR("Failed to setup the desktop texture");
return false; LG_UNLOCK(desktop->updateLock);
return;
} }
LG_UNLOCK(desktop->updateLock);
} }
if (!egl_texture_update_from_frame(desktop->texture, frame)) if (desktop->update)
return false;
enum EGL_TexStatus status;
if ((status = egl_texture_process(desktop->texture)) != EGL_TEX_STATUS_OK)
{ {
if (status != EGL_TEX_STATUS_NOTREADY) desktop->update = false;
DEBUG_ERROR("Failed to process the desktop texture"); egl_texture_update_from_frame(desktop->texture, desktop->frame);
} }
return true;
} }
bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y, const float scaleX, const float scaleY, const bool nearest) bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y, const float scaleX, const float scaleY, const bool nearest)
@@ -242,6 +262,9 @@ bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y, con
if (!desktop->shader) if (!desktop->shader)
return false; return false;
if (egl_texture_process(desktop->texture) != EGL_TEX_STATUS_OK)
return false;
const struct DesktopShader * shader = desktop->shader; const struct DesktopShader * shader = desktop->shader;
egl_shader_use(shader->shader); egl_shader_use(shader->shader);
glUniform4f(shader->uDesktopPos , x, y, scaleX, scaleY); glUniform4f(shader->uDesktopPos , x, y, scaleX, scaleY);
@@ -258,4 +281,4 @@ bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y, con
egl_model_render(desktop->model); egl_model_render(desktop->model);
return true; return true;
} }

View File

@@ -25,8 +25,9 @@ Place, Suite 330, Boston, MA 02111-1307 USA
typedef struct EGL_Desktop EGL_Desktop; typedef struct EGL_Desktop EGL_Desktop;
bool egl_desktop_init(void * egl, EGL_Desktop ** desktop); bool egl_desktop_init(EGL_Desktop ** desktop);
void egl_desktop_free(EGL_Desktop ** desktop); void egl_desktop_free(EGL_Desktop ** desktop);
bool egl_desktop_update(EGL_Desktop * desktop, const bool sourceChanged, const LG_RendererFormat format, const FrameBuffer * frame); bool egl_desktop_prepare_update(EGL_Desktop * desktop, const bool sourceChanged, const LG_RendererFormat format, const FrameBuffer frame);
bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y, const float scaleX, const float scaleY, const bool nearest); void egl_desktop_perform_update(EGL_Desktop * desktop, const bool sourceChanged);
bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y, const float scaleX, const float scaleY, const bool nearest);

View File

@@ -22,8 +22,6 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "common/debug.h" #include "common/debug.h"
#include "common/option.h" #include "common/option.h"
#include "common/sysinfo.h" #include "common/sysinfo.h"
#include "common/time.h"
#include "common/locking.h"
#include "utils.h" #include "utils.h"
#include "dynamic/fonts.h" #include "dynamic/fonts.h"
@@ -55,11 +53,12 @@ struct Inst
LG_RendererParams params; LG_RendererParams params;
struct Options opt; struct Options opt;
EGLNativeDisplayType nativeDisp;
EGLNativeWindowType nativeWind; EGLNativeWindowType nativeWind;
EGLDisplay display; EGLDisplay display;
EGLConfig configs; EGLConfig configs;
EGLSurface surface; EGLSurface surface;
EGLContext context, frameContext; EGLContext context;
EGL_Desktop * desktop; // the desktop EGL_Desktop * desktop; // the desktop
EGL_Cursor * cursor; // the mouse cursor EGL_Cursor * cursor; // the mouse cursor
@@ -68,7 +67,7 @@ struct Inst
EGL_Alert * alert; // the alert display EGL_Alert * alert; // the alert display
LG_RendererFormat format; LG_RendererFormat format;
bool start; bool sourceChanged;
uint64_t waitFadeTime; uint64_t waitFadeTime;
bool waitDone; bool waitDone;
@@ -110,7 +109,7 @@ static struct Option egl_options[] =
.name = "doubleBuffer", .name = "doubleBuffer",
.description = "Enable double buffering", .description = "Enable double buffering",
.type = OPTION_TYPE_BOOL, .type = OPTION_TYPE_BOOL,
.value.x_bool = false .value.x_bool = true
}, },
{ {
.module = "egl", .module = "egl",
@@ -199,7 +198,7 @@ bool egl_initialize(void * opaque, Uint32 * sdlFlags)
if (maxSamples > 4) if (maxSamples > 4)
maxSamples = 4; maxSamples = 4;
DEBUG_INFO("Multisampling enabled, max samples: %d", maxSamples); DEBUG_INFO("Multsampling enabled, max samples: %d", maxSamples);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, maxSamples); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, maxSamples);
} }
@@ -221,20 +220,9 @@ void egl_deinitialize(void * opaque)
egl_splash_free (&this->splash); egl_splash_free (&this->splash);
egl_alert_free (&this->alert ); egl_alert_free (&this->alert );
LG_LOCK_FREE(this->lock);
free(this); free(this);
} }
void egl_on_restart(void * opaque)
{
struct Inst * this = (struct Inst *)opaque;
eglDestroyContext(this->display, this->frameContext);
this->frameContext = NULL;
this->start = false;
}
void egl_on_resize(void * opaque, const int width, const int height, const LG_RendererRect destRect) void egl_on_resize(void * opaque, const int width, const int height, const LG_RendererRect destRect)
{ {
struct Inst * this = (struct Inst *)opaque; struct Inst * this = (struct Inst *)opaque;
@@ -308,49 +296,28 @@ bool egl_on_mouse_event(void * opaque, const bool visible, const int x, const in
return true; return true;
} }
bool egl_on_frame_event(void * opaque, const LG_RendererFormat format, const FrameBuffer * frame) bool egl_on_frame_event(void * opaque, const LG_RendererFormat format, const FrameBuffer frame)
{ {
struct Inst * this = (struct Inst *)opaque; struct Inst * this = (struct Inst *)opaque;
const bool sourceChanged = ( this->sourceChanged = (
this->sourceChanged ||
this->format.type != format.type || this->format.type != format.type ||
this->format.width != format.width || this->format.width != format.width ||
this->format.height != format.height || this->format.height != format.height ||
this->format.pitch != format.pitch this->format.pitch != format.pitch
); );
if (sourceChanged) if (this->sourceChanged)
memcpy(&this->format, &format, sizeof(LG_RendererFormat)); memcpy(&this->format, &format, sizeof(LG_RendererFormat));
this->useNearest = this->width < format.width || this->height < format.height; this->useNearest = this->width < format.width || this->height < format.height;
/* this event runs in a second thread so we need to init it here */ if (!egl_desktop_prepare_update(this->desktop, this->sourceChanged, format, frame))
if (!this->frameContext)
{ {
static EGLint attrs[] = { DEBUG_INFO("Failed to prepare to update the desktop");
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
if (!(this->frameContext = eglCreateContext(this->display, this->configs, this->context, attrs)))
{
DEBUG_ERROR("Failed to create the frame context");
return false;
}
if (!eglMakeCurrent(this->display, EGL_NO_SURFACE, EGL_NO_SURFACE, this->frameContext))
{
DEBUG_ERROR("Failed to make the frame context current");
return false;
}
}
if (!egl_desktop_update(this->desktop, sourceChanged, format, frame))
{
DEBUG_INFO("Failed to to update the desktop");
return false; return false;
} }
this->start = true;
return true; return true;
} }
@@ -401,26 +368,11 @@ bool egl_render_startup(void * opaque, SDL_Window * window)
return false; return false;
} }
const char *client_exts = eglQueryString(NULL, EGL_EXTENSIONS);
DEBUG_INFO("Supported extensions: %s", client_exts);
bool useNative = false;
if (strstr(client_exts, "EGL_KHR_platform_base") != NULL)
useNative = true;
DEBUG_INFO("use native: %s", useNative ? "true" : "false");
switch(wminfo.subsystem) switch(wminfo.subsystem)
{ {
case SDL_SYSWM_X11: case SDL_SYSWM_X11:
{ {
if (!useNative) this->nativeDisp = (EGLNativeDisplayType)wminfo.info.x11.display;
this->display = eglGetPlatformDisplay(EGL_PLATFORM_X11_KHR, wminfo.info.x11.display, NULL);
else
{
EGLNativeDisplayType native = (EGLNativeDisplayType)wminfo.info.x11.display;
this->display = eglGetDisplay(native);
}
this->nativeWind = (EGLNativeWindowType)wminfo.info.x11.window; this->nativeWind = (EGLNativeWindowType)wminfo.info.x11.window;
break; break;
} }
@@ -430,13 +382,7 @@ bool egl_render_startup(void * opaque, SDL_Window * window)
{ {
int width, height; int width, height;
SDL_GetWindowSize(window, &width, &height); SDL_GetWindowSize(window, &width, &height);
if (!useNative) this->nativeDisp = (EGLNativeDisplayType)wminfo.info.wl.display;
this->display = eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, wminfo.info.wl.display, NULL);
else
{
EGLNativeDisplayType native = (EGLNativeDisplayType)wminfo.info.wl.display;
this->display = eglGetDisplay(native);
}
this->nativeWind = (EGLNativeWindowType)wl_egl_window_create(wminfo.info.wl.surface, width, height); this->nativeWind = (EGLNativeWindowType)wl_egl_window_create(wminfo.info.wl.surface, width, height);
break; break;
} }
@@ -447,6 +393,7 @@ bool egl_render_startup(void * opaque, SDL_Window * window)
return false; return false;
} }
this->display = eglGetDisplay(this->nativeDisp);
if (this->display == EGL_NO_DISPLAY) if (this->display == EGL_NO_DISPLAY)
{ {
DEBUG_ERROR("eglGetDisplay failed"); DEBUG_ERROR("eglGetDisplay failed");
@@ -503,7 +450,7 @@ bool egl_render_startup(void * opaque, SDL_Window * window)
eglSwapInterval(this->display, this->opt.vsync ? 1 : 0); eglSwapInterval(this->display, this->opt.vsync ? 1 : 0);
if (!egl_desktop_init(this, &this->desktop)) if (!egl_desktop_init(&this->desktop))
{ {
DEBUG_ERROR("Failed to initialize the desktop"); DEBUG_ERROR("Failed to initialize the desktop");
return false; return false;
@@ -543,10 +490,7 @@ bool egl_render(void * opaque, SDL_Window * window)
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT);
if (this->start && egl_desktop_render(this->desktop, if (egl_desktop_render(this->desktop, this->translateX, this->translateY, this->scaleX, this->scaleY, this->useNearest))
this->translateX, this->translateY,
this->scaleX , this->scaleY ,
this->useNearest))
{ {
if (!this->waitFadeTime) if (!this->waitFadeTime)
this->waitFadeTime = microtime() + SPLASH_FADE_TIME; this->waitFadeTime = microtime() + SPLASH_FADE_TIME;
@@ -573,11 +517,6 @@ bool egl_render(void * opaque, SDL_Window * window)
if (!this->waitDone) if (!this->waitDone)
egl_splash_render(this->splash, a, this->splashRatio); egl_splash_render(this->splash, a, this->splashRatio);
} }
else
{
if (!this->start)
egl_splash_render(this->splash, 1.0f, this->splashRatio);
}
if (this->showAlert) if (this->showAlert)
{ {
@@ -595,6 +534,11 @@ bool egl_render(void * opaque, SDL_Window * window)
egl_fps_render(this->fps, this->screenScaleX, this->screenScaleY); egl_fps_render(this->fps, this->screenScaleX, this->screenScaleY);
eglSwapBuffers(this->display, this->surface); eglSwapBuffers(this->display, this->surface);
// defer texture uploads until after the flip to avoid stalling
egl_desktop_perform_update(this->desktop, this->sourceChanged);
this->sourceChanged = false;
return true; return true;
} }
@@ -614,7 +558,6 @@ struct LG_Renderer LGR_EGL =
.create = egl_create, .create = egl_create,
.initialize = egl_initialize, .initialize = egl_initialize,
.deinitialize = egl_deinitialize, .deinitialize = egl_deinitialize,
.on_restart = egl_on_restart,
.on_resize = egl_on_resize, .on_resize = egl_on_resize,
.on_mouse_shape = egl_on_mouse_shape, .on_mouse_shape = egl_on_mouse_shape,
.on_mouse_event = egl_on_mouse_event, .on_mouse_event = egl_on_mouse_event,
@@ -623,4 +566,4 @@ struct LG_Renderer LGR_EGL =
.render_startup = egl_render_startup, .render_startup = egl_render_startup,
.render = egl_render, .render = egl_render,
.update_fps = egl_update_fps .update_fps = egl_update_fps
}; };

View File

@@ -44,7 +44,6 @@ struct EGL_FPS
EGL_Model * model; EGL_Model * model;
bool ready; bool ready;
int iwidth, iheight;
float width, height; float width, height;
// uniforms // uniforms
@@ -145,22 +144,14 @@ void egl_fps_update(EGL_FPS * fps, const float avgFPS, const float renderFPS)
return; return;
} }
if (fps->iwidth != bmp->width || fps->iheight != bmp->height) egl_texture_setup(
{ fps->texture,
fps->iwidth = bmp->width; EGL_PF_BGRA,
fps->iheight = bmp->height; bmp->width ,
fps->width = (float)bmp->width; bmp->height,
fps->height = (float)bmp->height; bmp->width * bmp->bpp,
false
egl_texture_setup( );
fps->texture,
EGL_PF_BGRA,
bmp->width ,
bmp->height,
bmp->width * bmp->bpp,
false
);
}
egl_texture_update egl_texture_update
( (
@@ -168,7 +159,10 @@ void egl_fps_update(EGL_FPS * fps, const float avgFPS, const float renderFPS)
bmp->pixels bmp->pixels
); );
fps->width = bmp->width;
fps->height = bmp->height;
fps->ready = true; fps->ready = true;
fps->font->release(fps->fontObj, bmp); fps->font->release(fps->fontObj, bmp);
} }
@@ -193,4 +187,4 @@ void egl_fps_render(EGL_FPS * fps, const float scaleX, const float scaleY)
egl_model_render(fps->model); egl_model_render(fps->model);
glDisable(GL_BLEND); glDisable(GL_BLEND);
} }

View File

@@ -19,6 +19,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "texture.h" #include "texture.h"
#include "common/debug.h" #include "common/debug.h"
#include "common/locking.h"
#include "common/framebuffer.h" #include "common/framebuffer.h"
#include "debug.h" #include "debug.h"
#include "utils.h" #include "utils.h"
@@ -26,47 +27,33 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
#include <stdatomic.h>
#include <SDL2/SDL_egl.h> #include <SDL2/SDL_egl.h>
/* this must be a multiple of 2 */
#define TEXTURE_COUNT 2
struct Tex
{
GLuint t[3];
bool hasPBO;
GLuint pbo;
void * map;
GLsync sync;
};
struct TexState
{
_Atomic(uint8_t) w, u, s, d;
};
struct EGL_Texture struct EGL_Texture
{ {
enum EGL_PixelFormat pixFmt; enum EGL_PixelFormat pixFmt;
size_t width, height, stride; size_t width, height;
size_t bpp;
bool streaming; bool streaming;
bool ready; bool ready;
int planeCount; int textureCount;
GLuint textures[3];
GLuint samplers[3]; GLuint samplers[3];
size_t planes [3][3]; size_t planes[3][3];
GLintptr offsets [3]; GLintptr offsets[3];
GLenum intFormat; GLenum intFormat;
GLenum format; GLenum format;
GLenum dataType; GLenum dataType;
size_t pboBufferSize;
struct TexState state; bool hasPBO;
int textureCount; GLuint pbo[2];
struct Tex tex[TEXTURE_COUNT]; int pboRIndex;
int pboWIndex;
volatile int pboCount;
size_t pboBufferSize;
void * pboMap[2];
GLsync pboSync[2];
}; };
bool egl_texture_init(EGL_Texture ** texture) bool egl_texture_init(EGL_Texture ** texture)
@@ -79,6 +66,7 @@ bool egl_texture_init(EGL_Texture ** texture)
} }
memset(*texture, 0, sizeof(EGL_Texture)); memset(*texture, 0, sizeof(EGL_Texture));
return true; return true;
} }
@@ -87,102 +75,44 @@ void egl_texture_free(EGL_Texture ** texture)
if (!*texture) if (!*texture)
return; return;
if ((*texture)->planeCount > 0) if ((*texture)->textureCount > 0)
glDeleteSamplers((*texture)->planeCount, (*texture)->samplers);
for(int i = 0; i < (*texture)->textureCount; ++i)
{ {
struct Tex * t = &(*texture)->tex[i]; glDeleteTextures((*texture)->textureCount, (*texture)->textures);
if (t->hasPBO) glDeleteSamplers((*texture)->textureCount, (*texture)->samplers);
{ }
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, t->pbo);
if ((*texture)->tex[i].map) if ((*texture)->hasPBO)
{ {
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); for(int i = 0; i < 2; ++i)
(*texture)->tex[i].map = NULL; {
} glBindBuffer(GL_PIXEL_UNPACK_BUFFER, (*texture)->pbo[i]);
glDeleteBuffers(1, &t->pbo); glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
if (t->sync)
glDeleteSync(t->sync); if ((*texture)->pboSync[i])
} glDeleteSync((*texture)->pboSync[i]);
}
if ((*texture)->planeCount > 0) glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
glDeleteTextures((*texture)->planeCount, t->t); glDeleteBuffers(2, (*texture)->pbo);
} }
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
free(*texture); free(*texture);
*texture = NULL; *texture = NULL;
} }
static bool egl_texture_map(EGL_Texture * texture, uint8_t i)
{
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[i].pbo);
texture->tex[i].map = glMapBufferRange(
GL_PIXEL_UNPACK_BUFFER,
0,
texture->pboBufferSize,
GL_MAP_WRITE_BIT |
GL_MAP_UNSYNCHRONIZED_BIT |
GL_MAP_INVALIDATE_BUFFER_BIT
);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
if (!texture->tex[i].map)
{
EGL_ERROR("glMapBufferRange failed for %d of %lu bytes", i, texture->pboBufferSize);
return false;
}
return true;
}
static void egl_texture_unmap(EGL_Texture * texture, uint8_t i)
{
if (!texture->tex[i].map)
return;
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[i].pbo);
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
texture->tex[i].map = NULL;
}
bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_t width, size_t height, size_t stride, bool streaming) bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_t width, size_t height, size_t stride, bool streaming)
{ {
int planeCount; int textureCount;
if (texture->streaming) texture->pixFmt = pixFmt;
{ texture->width = width;
for(int i = 0; i < texture->textureCount; ++i) texture->height = height;
{ texture->streaming = streaming;
egl_texture_unmap(texture, i); texture->ready = false;
if (texture->tex[i].hasPBO)
{
glDeleteBuffers(1, &texture->tex[i].pbo);
texture->tex[i].hasPBO = false;
}
}
}
texture->pixFmt = pixFmt;
texture->width = width;
texture->height = height;
texture->stride = stride;
texture->streaming = streaming;
texture->textureCount = streaming ? TEXTURE_COUNT : 1;
texture->ready = false;
atomic_store_explicit(&texture->state.w, 0, memory_order_relaxed);
atomic_store_explicit(&texture->state.u, 0, memory_order_relaxed);
atomic_store_explicit(&texture->state.s, 0, memory_order_relaxed);
atomic_store_explicit(&texture->state.d, 0, memory_order_relaxed);
switch(pixFmt) switch(pixFmt)
{ {
case EGL_PF_BGRA: case EGL_PF_BGRA:
planeCount = 1; textureCount = 1;
texture->bpp = 4;
texture->format = GL_BGRA; texture->format = GL_BGRA;
texture->planes[0][0] = width; texture->planes[0][0] = width;
texture->planes[0][1] = height; texture->planes[0][1] = height;
@@ -194,8 +124,7 @@ bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_
break; break;
case EGL_PF_RGBA: case EGL_PF_RGBA:
planeCount = 1; textureCount = 1;
texture->bpp = 4;
texture->format = GL_RGBA; texture->format = GL_RGBA;
texture->planes[0][0] = width; texture->planes[0][0] = width;
texture->planes[0][1] = height; texture->planes[0][1] = height;
@@ -207,8 +136,7 @@ bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_
break; break;
case EGL_PF_RGBA10: case EGL_PF_RGBA10:
planeCount = 1; textureCount = 1;
texture->bpp = 4;
texture->format = GL_RGBA; texture->format = GL_RGBA;
texture->planes[0][0] = width; texture->planes[0][0] = width;
texture->planes[0][1] = height; texture->planes[0][1] = height;
@@ -220,8 +148,7 @@ bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_
break; break;
case EGL_PF_YUV420: case EGL_PF_YUV420:
planeCount = 3; textureCount = 3;
texture->bpp = 4;
texture->format = GL_RED; texture->format = GL_RED;
texture->planes[0][0] = width; texture->planes[0][0] = width;
texture->planes[0][1] = height; texture->planes[0][1] = height;
@@ -244,139 +171,128 @@ bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_
return false; return false;
} }
if (planeCount > texture->planeCount) if (textureCount > texture->textureCount)
{ {
if (texture->planeCount > 0) if (texture->textureCount > 0)
glDeleteSamplers(texture->planeCount, texture->samplers);
for(int i = 0; i < texture->textureCount; ++i)
{ {
if (texture->planeCount > 0) glDeleteTextures(texture->textureCount, texture->textures);
glDeleteTextures(texture->planeCount, texture->tex[i].t); glDeleteSamplers(texture->textureCount, texture->samplers);
glGenTextures(planeCount, texture->tex[i].t);
} }
glGenSamplers(planeCount, texture->samplers); texture->textureCount = textureCount;
for(int p = 0; p < planeCount; ++p) glGenTextures(texture->textureCount, texture->textures);
{ glGenSamplers(texture->textureCount, texture->samplers);
glSamplerParameteri(texture->samplers[p], GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glSamplerParameteri(texture->samplers[p], GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glSamplerParameteri(texture->samplers[p], GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE);
glSamplerParameteri(texture->samplers[p], GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE);
}
texture->planeCount = planeCount;
} }
for(int i = 0; i < texture->textureCount; ++i) for(int i = 0; i < textureCount; ++i)
{ {
for(int p = 0; p < planeCount; ++p) glSamplerParameteri(texture->samplers[i], GL_TEXTURE_MIN_FILTER, GL_LINEAR);
{ glSamplerParameteri(texture->samplers[i], GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, texture->tex[i].t[p]); glSamplerParameteri(texture->samplers[i], GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, texture->intFormat, texture->planes[p][0], glSamplerParameteri(texture->samplers[i], GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE);
texture->planes[p][1], 0, texture->format, texture->dataType, NULL);
} glBindTexture(GL_TEXTURE_2D, texture->textures[i]);
glTexImage2D(GL_TEXTURE_2D, 0, texture->intFormat, texture->planes[i][0], texture->planes[i][1],
0, texture->format, texture->dataType, NULL);
} }
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D, 0);
if (!streaming) if (streaming)
return true;
for(int i = 0; i < texture->textureCount; ++i)
{ {
glGenBuffers(1, &texture->tex[i].pbo); if (texture->hasPBO)
texture->tex[i].hasPBO = true; {
// release old PBOs and delete the buffers
for(int i = 0; i < 2; ++i)
{
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->pbo[i]);
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
}
glDeleteBuffers(2, texture->pbo);
}
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[i].pbo); glGenBuffers(2, texture->pbo);
glBufferStorage( texture->hasPBO = true;
GL_PIXEL_UNPACK_BUFFER, for(int i = 0; i < 2; ++i)
texture->pboBufferSize, {
NULL, glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->pbo[i]);
GL_MAP_WRITE_BIT glBufferStorage(
); GL_PIXEL_UNPACK_BUFFER,
texture->pboBufferSize,
NULL,
GL_MAP_PERSISTENT_BIT |
GL_MAP_WRITE_BIT
);
texture->pboMap[i] = glMapBufferRange(
GL_PIXEL_UNPACK_BUFFER,
0,
texture->pboBufferSize,
GL_MAP_PERSISTENT_BIT |
GL_MAP_WRITE_BIT |
GL_MAP_UNSYNCHRONIZED_BIT |
GL_MAP_INVALIDATE_BUFFER_BIT |
GL_MAP_FLUSH_EXPLICIT_BIT
);
if (!texture->pboMap[i])
{
EGL_ERROR("glMapBufferRange failed for %d of %lu bytes", i, texture->pboBufferSize);
return false;
}
}
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
} }
return true; return true;
} }
static void egl_warn_slow()
{
static bool warnDone = false;
if (!warnDone)
{
warnDone = true;
DEBUG_BREAK();
DEBUG_WARN("The guest is providing updates faster then your computer can display them");
DEBUG_WARN("This is a hardware limitation, expect microstutters & frame skips");
DEBUG_BREAK();
}
}
bool egl_texture_update(EGL_Texture * texture, const uint8_t * buffer) bool egl_texture_update(EGL_Texture * texture, const uint8_t * buffer)
{ {
if (texture->streaming) if (texture->streaming)
{ {
const uint8_t sw = /* NOTE: DO NOT use any gl commands here as streaming must be thread safe */
atomic_load_explicit(&texture->state.w, memory_order_acquire);
if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == (uint8_t)(sw + 1)) if (texture->pboCount == 2)
{
egl_warn_slow();
return true; return true;
}
const uint8_t t = sw % TEXTURE_COUNT; /* update the GPU buffer */
if (!egl_texture_map(texture, t)) memcpy(texture->pboMap[texture->pboWIndex], buffer, texture->pboBufferSize);
return EGL_TEX_STATUS_ERROR; texture->pboSync[texture->pboWIndex] = 0;
memcpy(texture->tex[t].map, buffer, texture->pboBufferSize); if (++texture->pboWIndex == 2)
atomic_fetch_add_explicit(&texture->state.w, 1, memory_order_release); texture->pboWIndex = 0;
egl_texture_unmap(texture, t); INTERLOCKED_INC(&texture->pboCount);
} }
else else
{ {
for(int p = 0; p < texture->planeCount; ++p) /* Non streaming, this is NOT thread safe */
for(int i = 0; i < texture->textureCount; ++i)
{ {
glBindTexture(GL_TEXTURE_2D, texture->tex[0].t[p]); glBindTexture(GL_TEXTURE_2D, texture->textures[i]);
glPixelStorei(GL_UNPACK_ROW_LENGTH, texture->planes[p][0]); glPixelStorei(GL_UNPACK_ROW_LENGTH, texture->planes[i][0]);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture->planes[p][0], texture->planes[p][1], glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture->planes[i][0], texture->planes[i][1],
texture->format, texture->dataType, buffer + texture->offsets[p]); texture->format, texture->dataType, buffer + texture->offsets[i]);
} }
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D, 0);
} }
return true; return true;
} }
bool egl_texture_update_from_frame(EGL_Texture * texture, const FrameBuffer * frame) bool egl_texture_update_from_frame(EGL_Texture * texture, const FrameBuffer frame)
{ {
if (!texture->streaming) if (!texture->streaming)
return false; return false;
const uint8_t sw = if (texture->pboCount == 2)
atomic_load_explicit(&texture->state.w, memory_order_acquire);
if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == (uint8_t)(sw + 1))
{
egl_warn_slow();
return true; return true;
}
const uint8_t t = sw % TEXTURE_COUNT; framebuffer_read(frame, texture->pboMap[texture->pboWIndex], texture->pboBufferSize);
if (!egl_texture_map(texture, t)) texture->pboSync[texture->pboWIndex] = 0;
return EGL_TEX_STATUS_ERROR;
framebuffer_read( if (++texture->pboWIndex == 2)
frame, texture->pboWIndex = 0;
texture->tex[t].map, INTERLOCKED_INC(&texture->pboCount);
texture->stride,
texture->height,
texture->width,
texture->bpp,
texture->stride
);
atomic_fetch_add_explicit(&texture->state.w, 1, memory_order_release);
egl_texture_unmap(texture, t);
return true; return true;
} }
@@ -386,87 +302,78 @@ enum EGL_TexStatus egl_texture_process(EGL_Texture * texture)
if (!texture->streaming) if (!texture->streaming)
return EGL_TEX_STATUS_OK; return EGL_TEX_STATUS_OK;
const uint8_t su = if (texture->pboCount == 0)
atomic_load_explicit(&texture->state.u, memory_order_acquire);
const uint8_t nextu = su + 1;
if (
su == atomic_load_explicit(&texture->state.w, memory_order_acquire) ||
nextu == atomic_load_explicit(&texture->state.s, memory_order_acquire) ||
nextu == atomic_load_explicit(&texture->state.d, memory_order_acquire))
return texture->ready ? EGL_TEX_STATUS_OK : EGL_TEX_STATUS_NOTREADY; return texture->ready ? EGL_TEX_STATUS_OK : EGL_TEX_STATUS_NOTREADY;
/* update the texture */ /* process any buffers that have not yet been flushed */
const uint8_t t = su % TEXTURE_COUNT; int pos = texture->pboRIndex;
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[t].pbo); for(int i = 0; i < texture->pboCount; ++i)
for(int p = 0; p < texture->planeCount; ++p)
{ {
glBindTexture(GL_TEXTURE_2D, texture->tex[t].t[p]); if (texture->pboSync[pos] == 0)
glPixelStorei(GL_UNPACK_ROW_LENGTH, texture->planes[p][2]); {
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture->planes[p][0], texture->planes[p][1], glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->pbo[pos]);
texture->format, texture->dataType, (const void *)texture->offsets[p]); glFlushMappedBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, texture->pboBufferSize);
texture->pboSync[pos] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
}
if (++pos == 2)
pos = 0;
} }
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
/* create a fence to prevent usage before the update is complete */ /* wait for the buffer to be ready */
texture->tex[t].sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); pos = texture->pboRIndex;
switch(glClientWaitSync(texture->pboSync[pos], 0, 0))
{
case GL_ALREADY_SIGNALED:
case GL_CONDITION_SATISFIED:
break;
/* we must flush to ensure the sync is in the command buffer */ case GL_TIMEOUT_EXPIRED:
glFlush(); return texture->ready ? EGL_TEX_STATUS_OK : EGL_TEX_STATUS_NOTREADY;
case GL_WAIT_FAILED:
glDeleteSync(texture->pboSync[pos]);
EGL_ERROR("glClientWaitSync failed");
return EGL_TEX_STATUS_ERROR;
}
/* delete the sync and bind the buffer */
glDeleteSync(texture->pboSync[pos]);
texture->pboSync[pos] = 0;
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->pbo[pos]);
/* update the textures */
for(int i = 0; i < texture->textureCount; ++i)
{
glBindTexture(GL_TEXTURE_2D, texture->textures[i]);
glPixelStorei(GL_UNPACK_ROW_LENGTH, texture->planes[i][2]);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture->planes[i][0], texture->planes[i][1],
texture->format, texture->dataType, (const void *)texture->offsets[i]);
}
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
/* advance the read index */
if (++texture->pboRIndex == 2)
texture->pboRIndex = 0;
INTERLOCKED_DEC(&texture->pboCount);
texture->ready = true; texture->ready = true;
atomic_fetch_add_explicit(&texture->state.u, 1, memory_order_release);
return EGL_TEX_STATUS_OK; return EGL_TEX_STATUS_OK;
} }
enum EGL_TexStatus egl_texture_bind(EGL_Texture * texture) enum EGL_TexStatus egl_texture_bind(EGL_Texture * texture)
{ {
uint8_t ss = atomic_load_explicit(&texture->state.s, memory_order_acquire); /* if there are no new buffers ready, then just bind the textures */
uint8_t sd = atomic_load_explicit(&texture->state.d, memory_order_acquire); if (texture->streaming && !texture->ready)
return EGL_TEX_STATUS_NOTREADY;
if (texture->streaming) for(int i = 0; i < texture->textureCount; ++i)
{
if (!texture->ready)
return EGL_TEX_STATUS_NOTREADY;
const uint8_t t = ss % TEXTURE_COUNT;
if (texture->tex[t].sync != 0)
{
switch(glClientWaitSync(texture->tex[t].sync, 0, 20000000)) // 20ms
{
case GL_ALREADY_SIGNALED:
case GL_CONDITION_SATISFIED:
glDeleteSync(texture->tex[t].sync);
texture->tex[t].sync = 0;
ss = atomic_fetch_add_explicit(&texture->state.s, 1,
memory_order_release) + 1;
break;
case GL_TIMEOUT_EXPIRED:
break;
case GL_WAIT_FAILED:
case GL_INVALID_VALUE:
glDeleteSync(texture->tex[t].sync);
texture->tex[t].sync = 0;
EGL_ERROR("glClientWaitSync failed");
return EGL_TEX_STATUS_ERROR;
}
}
if (ss != sd && ss != (uint8_t)(sd + 1))
sd = atomic_fetch_add_explicit(&texture->state.d, 1,
memory_order_release) + 1;
}
const uint8_t t = sd % TEXTURE_COUNT;
for(int i = 0; i < texture->planeCount; ++i)
{ {
glActiveTexture(GL_TEXTURE0 + i); glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(GL_TEXTURE_2D, texture->tex[t].t[i]); glBindTexture(GL_TEXTURE_2D, texture->textures[i]);
glBindSampler(i, texture->samplers[i]); glBindSampler(i, texture->samplers[i]);
} }
@@ -475,5 +382,5 @@ enum EGL_TexStatus egl_texture_bind(EGL_Texture * texture)
int egl_texture_count(EGL_Texture * texture) int egl_texture_count(EGL_Texture * texture)
{ {
return texture->planeCount; return texture->textureCount;
} }

View File

@@ -47,7 +47,7 @@ void egl_texture_free(EGL_Texture ** tex);
bool egl_texture_setup (EGL_Texture * texture, enum EGL_PixelFormat pixfmt, size_t width, size_t height, size_t stride, bool streaming); bool egl_texture_setup (EGL_Texture * texture, enum EGL_PixelFormat pixfmt, size_t width, size_t height, size_t stride, bool streaming);
bool egl_texture_update (EGL_Texture * texture, const uint8_t * buffer); bool egl_texture_update (EGL_Texture * texture, const uint8_t * buffer);
bool egl_texture_update_from_frame(EGL_Texture * texture, const FrameBuffer * frame); bool egl_texture_update_from_frame(EGL_Texture * texture, const FrameBuffer frame);
enum EGL_TexStatus egl_texture_process(EGL_Texture * texture); enum EGL_TexStatus egl_texture_process(EGL_Texture * texture);
enum EGL_TexStatus egl_texture_bind (EGL_Texture * texture); enum EGL_TexStatus egl_texture_bind (EGL_Texture * texture);
int egl_texture_count (EGL_Texture * texture); int egl_texture_count (EGL_Texture * texture);

View File

@@ -33,7 +33,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "common/debug.h" #include "common/debug.h"
#include "common/option.h" #include "common/option.h"
#include "common/framebuffer.h" #include "common/framebuffer.h"
#include "common/locking.h" #include "utils.h"
#include "dynamic/fonts.h" #include "dynamic/fonts.h"
#include "ll.h" #include "ll.h"
@@ -77,8 +77,7 @@ static struct Option opengl_options[] =
.description = "Use GL_AMD_pinned_memory if it is available", .description = "Use GL_AMD_pinned_memory if it is available",
.type = OPTION_TYPE_BOOL, .type = OPTION_TYPE_BOOL,
.value.x_bool = true .value.x_bool = true
}, }
{0}
}; };
struct OpenGL_Options struct OpenGL_Options
@@ -117,14 +116,14 @@ struct Inst
const LG_Font * font; const LG_Font * font;
LG_FontObj fontObj, alertFontObj; LG_FontObj fontObj, alertFontObj;
LG_Lock formatLock; LG_Lock formatLock;
LG_RendererFormat format; LG_RendererFormat format;
GLuint intFormat; GLuint intFormat;
GLuint vboFormat; GLuint vboFormat;
GLuint dataFormat; GLuint dataFormat;
size_t texSize; size_t texSize;
size_t texPos; size_t texPos;
const FrameBuffer * frame; FrameBuffer frame;
uint64_t drawStart; uint64_t drawStart;
bool hasBuffers; bool hasBuffers;
@@ -170,15 +169,8 @@ struct Inst
static bool _check_gl_error(unsigned int line, const char * name); static bool _check_gl_error(unsigned int line, const char * name);
#define check_gl_error(name) _check_gl_error(__LINE__, name) #define check_gl_error(name) _check_gl_error(__LINE__, name)
enum ConfigStatus
{
CONFIG_STATUS_OK,
CONFIG_STATUS_ERROR,
CONFIG_STATUS_NOOP
};
static void deconfigure(struct Inst * this); static void deconfigure(struct Inst * this);
static enum ConfigStatus configure(struct Inst * this, SDL_Window *window); static bool configure(struct Inst * this, SDL_Window *window);
static void update_mouse_shape(struct Inst * this, bool * newShape); static void update_mouse_shape(struct Inst * this, bool * newShape);
static bool draw_frame(struct Inst * this); static bool draw_frame(struct Inst * this);
static void draw_mouse(struct Inst * this); static void draw_mouse(struct Inst * this);
@@ -295,12 +287,6 @@ void opengl_deinitialize(void * opaque)
free(this); free(this);
} }
void opengl_on_restart(void * opaque)
{
struct Inst * this = (struct Inst *)opaque;
this->waiting = true;
}
void opengl_on_resize(void * opaque, const int width, const int height, const LG_RendererRect destRect) void opengl_on_resize(void * opaque, const int width, const int height, const LG_RendererRect destRect)
{ {
struct Inst * this = (struct Inst *)opaque; struct Inst * this = (struct Inst *)opaque;
@@ -375,7 +361,7 @@ bool opengl_on_mouse_event(void * opaque, const bool visible, const int x, const
return false; return false;
} }
bool opengl_on_frame_event(void * opaque, const LG_RendererFormat format, const FrameBuffer * frame) bool opengl_on_frame_event(void * opaque, const LG_RendererFormat format, const FrameBuffer frame)
{ {
struct Inst * this = (struct Inst *)opaque; struct Inst * this = (struct Inst *)opaque;
if (!this) if (!this)
@@ -563,18 +549,10 @@ bool opengl_render(void * opaque, SDL_Window * window)
if (!this) if (!this)
return false; return false;
switch(configure(this, window)) if (configure(this, window))
{ if (!draw_frame(this))
case CONFIG_STATUS_ERROR:
DEBUG_ERROR("configure failed");
return false; return false;
case CONFIG_STATUS_NOOP :
case CONFIG_STATUS_OK :
if (!draw_frame(this))
return false;
}
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT);
@@ -829,7 +807,6 @@ const LG_Renderer LGR_OpenGL =
.create = opengl_create, .create = opengl_create,
.initialize = opengl_initialize, .initialize = opengl_initialize,
.deinitialize = opengl_deinitialize, .deinitialize = opengl_deinitialize,
.on_restart = opengl_on_restart,
.on_resize = opengl_on_resize, .on_resize = opengl_on_resize,
.on_mouse_shape = opengl_on_mouse_shape, .on_mouse_shape = opengl_on_mouse_shape,
.on_mouse_event = opengl_on_mouse_event, .on_mouse_event = opengl_on_mouse_event,
@@ -851,13 +828,13 @@ static bool _check_gl_error(unsigned int line, const char * name)
return true; return true;
} }
static enum ConfigStatus configure(struct Inst * this, SDL_Window *window) static bool configure(struct Inst * this, SDL_Window *window)
{ {
LG_LOCK(this->formatLock); LG_LOCK(this->formatLock);
if (!this->reconfigure) if (!this->reconfigure)
{ {
LG_UNLOCK(this->formatLock); LG_UNLOCK(this->formatLock);
return CONFIG_STATUS_NOOP; return this->configured;
} }
if (this->configured) if (this->configured)
@@ -885,7 +862,7 @@ static enum ConfigStatus configure(struct Inst * this, SDL_Window *window)
default: default:
DEBUG_ERROR("Unknown/unsupported compression type"); DEBUG_ERROR("Unknown/unsupported compression type");
return CONFIG_STATUS_ERROR; return false;
} }
// calculate the texture size in bytes // calculate the texture size in bytes
@@ -903,36 +880,30 @@ static enum ConfigStatus configure(struct Inst * this, SDL_Window *window)
if (this->amdPinnedMemSupport) if (this->amdPinnedMemSupport)
{ {
const int pagesize = getpagesize(); const int pagesize = getpagesize();
this->texPixels[0] = memalign(pagesize, this->texSize * BUFFER_COUNT);
memset(this->texPixels[0], 0, this->texSize * BUFFER_COUNT);
for(int i = 1; i < BUFFER_COUNT; ++i)
this->texPixels[i] = this->texPixels[0] + this->texSize;
for(int i = 0; i < BUFFER_COUNT; ++i) for(int i = 0; i < BUFFER_COUNT; ++i)
{ {
this->texPixels[i] = aligned_alloc(pagesize, this->texSize);
if (!this->texPixels[i])
{
DEBUG_ERROR("Failed to allocate memory for texture");
return CONFIG_STATUS_ERROR;
}
memset(this->texPixels[i], 0, this->texSize);
glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, this->vboID[i]); glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, this->vboID[i]);
if (check_gl_error("glBindBuffer")) if (check_gl_error("glBindBuffer"))
{ {
LG_UNLOCK(this->formatLock); LG_UNLOCK(this->formatLock);
return CONFIG_STATUS_ERROR; return false;
} }
glBufferData( glBufferData(
GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD,
this->texSize, this->texSize,
this->texPixels[i], this->texPixels[i],
GL_STREAM_DRAW GL_STREAM_DRAW);
);
if (check_gl_error("glBufferData")) if (check_gl_error("glBufferData"))
{ {
LG_UNLOCK(this->formatLock); LG_UNLOCK(this->formatLock);
return CONFIG_STATUS_ERROR; return false;
} }
} }
glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, 0); glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, 0);
@@ -945,7 +916,7 @@ static enum ConfigStatus configure(struct Inst * this, SDL_Window *window)
if (check_gl_error("glBindBuffer")) if (check_gl_error("glBindBuffer"))
{ {
LG_UNLOCK(this->formatLock); LG_UNLOCK(this->formatLock);
return CONFIG_STATUS_ERROR; return false;
} }
glBufferData( glBufferData(
@@ -957,7 +928,7 @@ static enum ConfigStatus configure(struct Inst * this, SDL_Window *window)
if (check_gl_error("glBufferData")) if (check_gl_error("glBufferData"))
{ {
LG_UNLOCK(this->formatLock); LG_UNLOCK(this->formatLock);
return CONFIG_STATUS_ERROR; return false;
} }
} }
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
@@ -968,7 +939,7 @@ static enum ConfigStatus configure(struct Inst * this, SDL_Window *window)
if (check_gl_error("glGenTextures")) if (check_gl_error("glGenTextures"))
{ {
LG_UNLOCK(this->formatLock); LG_UNLOCK(this->formatLock);
return CONFIG_STATUS_ERROR; return false;
} }
this->hasFrames = true; this->hasFrames = true;
@@ -979,7 +950,7 @@ static enum ConfigStatus configure(struct Inst * this, SDL_Window *window)
if (check_gl_error("glBindTexture")) if (check_gl_error("glBindTexture"))
{ {
LG_UNLOCK(this->formatLock); LG_UNLOCK(this->formatLock);
return CONFIG_STATUS_ERROR; return false;
} }
glTexImage2D( glTexImage2D(
@@ -996,7 +967,7 @@ static enum ConfigStatus configure(struct Inst * this, SDL_Window *window)
if (check_gl_error("glTexImage2D")) if (check_gl_error("glTexImage2D"))
{ {
LG_UNLOCK(this->formatLock); LG_UNLOCK(this->formatLock);
return CONFIG_STATUS_ERROR; return false;
} }
// configure the texture // configure the texture
@@ -1027,7 +998,7 @@ static enum ConfigStatus configure(struct Inst * this, SDL_Window *window)
this->reconfigure = false; this->reconfigure = false;
LG_UNLOCK(this->formatLock); LG_UNLOCK(this->formatLock);
return CONFIG_STATUS_OK; return true;
} }
static void deconfigure(struct Inst * this) static void deconfigure(struct Inst * this)
@@ -1055,6 +1026,9 @@ static void deconfigure(struct Inst * this)
if (this->amdPinnedMemSupport) if (this->amdPinnedMemSupport)
{ {
if (this->texPixels[0])
free(this->texPixels[0]);
for(int i = 0; i < BUFFER_COUNT; ++i) for(int i = 0; i < BUFFER_COUNT; ++i)
{ {
if (this->fences[i]) if (this->fences[i])
@@ -1062,12 +1036,7 @@ static void deconfigure(struct Inst * this)
glDeleteSync(this->fences[i]); glDeleteSync(this->fences[i]);
this->fences[i] = NULL; this->fences[i] = NULL;
} }
this->texPixels[i] = NULL;
if (this->texPixels[i])
{
free(this->texPixels[i]);
this->texPixels[i] = NULL;
}
} }
} }
@@ -1281,19 +1250,14 @@ static bool draw_frame(struct Inst * this)
glBindTexture(GL_TEXTURE_2D, this->frames[this->texIndex]); glBindTexture(GL_TEXTURE_2D, this->frames[this->texIndex]);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vboID[this->texIndex]); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vboID[this->texIndex]);
const int bpp = this->format.bpp / 8; glPixelStorei(GL_UNPACK_ALIGNMENT , 4);
glPixelStorei(GL_UNPACK_ALIGNMENT , bpp); glPixelStorei(GL_UNPACK_ROW_LENGTH , this->format.stride);
glPixelStorei(GL_UNPACK_ROW_LENGTH, this->format.width);
this->texPos = 0; this->texPos = 0;
framebuffer_read_fn( framebuffer_read_fn(
this->frame, this->frame,
this->format.height,
this->format.width,
bpp,
this->format.pitch,
opengl_buffer_fn, opengl_buffer_fn,
this->format.height * this->format.stride,
this this
); );
@@ -1355,4 +1319,4 @@ static void draw_mouse(struct Inst * this)
glTranslatef(this->mousePos.x, this->mousePos.y, 0.0f); glTranslatef(this->mousePos.x, this->mousePos.y, 0.0f);
glCallList(this->mouseList); glCallList(this->mouseList);
glPopMatrix(); glPopMatrix();
} }

View File

@@ -0,0 +1,30 @@
cmake_minimum_required(VERSION 3.0)
project(spice LANGUAGES C)
find_package(PkgConfig)
pkg_check_modules(SPICE_PKGCONFIG REQUIRED
spice-protocol
nettle
hogweed
)
add_definitions(-D USE_NETTLE)
add_library(spice STATIC
src/spice.c
src/rsa.c
)
target_link_libraries(spice
lg_common
${SPICE_PKGCONFIG_LIBRARIES}
)
target_include_directories(spice
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
PRIVATE
src
${SPICE_PKGCONFIG_INCLUDE_DIRS}
)

View File

@@ -0,0 +1,64 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <sys/types.h>
#include <stdbool.h>
#include <stdint.h>
typedef enum SpiceDataType
{
SPICE_DATA_TEXT,
SPICE_DATA_PNG,
SPICE_DATA_BMP,
SPICE_DATA_TIFF,
SPICE_DATA_JPEG,
SPICE_DATA_NONE
}
SpiceDataType;
typedef void (*SpiceClipboardNotice )(const SpiceDataType type);
typedef void (*SpiceClipboardData )(const SpiceDataType type, uint8_t * buffer, uint32_t size);
typedef void (*SpiceClipboardRelease)();
typedef void (*SpiceClipboardRequest)(const SpiceDataType type);
bool spice_connect(const char * host, const unsigned 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);
bool spice_clipboard_request(SpiceDataType type);
bool spice_clipboard_grab (SpiceDataType type);
bool spice_clipboard_release();
bool spice_clipboard_data (SpiceDataType type, uint8_t * data, size_t size);
/* events */
bool spice_set_clipboard_cb(
SpiceClipboardNotice cbNoticeFn,
SpiceClipboardData cbDataFn,
SpiceClipboardRelease cbReleaseFn,
SpiceClipboardRequest cbRequestFn);

146
client/spice/src/messages.h Normal file
View File

@@ -0,0 +1,146 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <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 SpiceChannelID
{
uint8_t type;
uint8_t channel_id;
}
SpiceChannelID;
typedef struct SpiceMsgMainChannelsList
{
uint32_t num_of_channels;
//SpiceChannelID channels[num_of_channels]
}
SpiceMainChannelsList;
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;
// spice is missing these defines, the offical reference library incorrectly uses the VD defines
#define COMMON_CAPS_BYTES (((SPICE_COMMON_CAP_MINI_HEADER + 32) / 8) & ~3)
#define COMMON_SET_CAPABILITY(caps, index) \
{ (caps)[(index) / 32] |= (1 << ((index) % 32)); }
#define MAIN_CAPS_BYTES (((SPICE_MAIN_CAP_SEAMLESS_MIGRATE + 32) / 8) & ~3)
#define MAIN_SET_CAPABILITY(caps, index) \
{ (caps)[(index) / 32] |= (1 << ((index) % 32)); }
#pragma pack(pop)

227
client/spice/src/rsa.c Normal file
View File

@@ -0,0 +1,227 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "rsa.h"
#include "common/debug.h"
#include <spice/protocol.h>
#include <malloc.h>
#include <string.h>
#if defined(USE_OPENSSL) && defined(USE_NETTLE)
#error "USE_OPENSSL and USE_NETTLE are both defined"
#elif !defined(USE_OPENSSL) && !defined(USE_NETTLE)
#error "One of USE_OPENSSL or USE_NETTLE must be defined"
#endif
#if defined(USE_OPENSSL)
#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <openssl/x509.h>
#endif
#if defined(USE_NETTLE)
#include <stdlib.h>
#include <nettle/asn1.h>
#include <nettle/sha1.h>
#include <nettle/rsa.h>
#include <nettle/bignum.h>
#include <gmp.h>
#define SHA1_HASH_LEN 20
#endif
#if defined(USE_NETTLE)
/* the below OAEP implementation is derived from the FreeTDS project */
static void memxor(uint8_t * a, const uint8_t * b, const unsigned int len)
{
for(unsigned int i = 0; i < len; ++i)
a[i] = a[i] ^ b[i];
}
static void sha1(uint8_t * hash, const uint8_t *data, unsigned int len)
{
struct sha1_ctx ctx;
sha1_init(&ctx);
sha1_update(&ctx, len, data);
sha1_digest(&ctx, SHA1_HASH_LEN, hash);
}
static void oaep_mask(uint8_t * dest, size_t dest_len, const uint8_t * mask, size_t mask_len)
{
uint8_t hash[SHA1_HASH_LEN];
uint8_t seed[mask_len + 4 ];
memcpy(seed, mask, mask_len);
for(unsigned int n = 0;; ++n)
{
(seed+mask_len)[0] = n >> 24;
(seed+mask_len)[1] = n >> 16;
(seed+mask_len)[2] = n >> 8;
(seed+mask_len)[3] = n >> 0;
sha1(hash, seed, sizeof(seed));
if (dest_len <= SHA1_HASH_LEN)
{
memxor(dest, hash, dest_len);
break;
}
memxor(dest, hash, SHA1_HASH_LEN);
dest += SHA1_HASH_LEN;
dest_len -= SHA1_HASH_LEN;
}
}
static bool oaep_pad(mpz_t m, unsigned int key_size, const uint8_t * message, unsigned int len)
{
if (len + SHA1_HASH_LEN * 2 + 2 > key_size)
{
DEBUG_ERROR("Message too long");
return false;
}
struct
{
uint8_t all[1];
uint8_t ros[SHA1_HASH_LEN];
uint8_t db [key_size - SHA1_HASH_LEN - 1];
} em;
memset(&em, 0, sizeof(em));
sha1(em.db, (uint8_t *)"", 0);
em.all[key_size - len - 1] = 0x1;
memcpy(em.all + (key_size - len), message, len);
/* we are not too worried about randomness since we are just making a local
* connection, should anyone use this code outside of LookingGlass please be
* sure to use something better such as `gnutls_rnd` */
for(int i = 0; i < SHA1_HASH_LEN; ++i)
em.ros[i] = rand() % 255;
const int db_len = key_size - SHA1_HASH_LEN - 1;
oaep_mask(em.db , db_len , em.ros, SHA1_HASH_LEN);
oaep_mask(em.ros, SHA1_HASH_LEN, em.db , db_len );
nettle_mpz_set_str_256_u(m, key_size, em.all);
return true;
}
#endif
bool spice_rsa_encrypt_password(uint8_t * pub_key, char * password, struct spice_password * result)
{
result->size = 0;
result->data = NULL;
#if defined(USE_OPENSSL)
BIO *bioKey = BIO_new(BIO_s_mem());
if (!bioKey)
{
DEBUG_ERROR("failed to allocate bioKey");
return false;
}
BIO_write(bioKey, pub_key, SPICE_TICKET_PUBKEY_BYTES);
EVP_PKEY *rsaKey = d2i_PUBKEY_bio(bioKey, NULL);
RSA *rsa = EVP_PKEY_get1_RSA(rsaKey);
result->size = RSA_size(rsa);
result->data = (char *)malloc(result->size);
if (RSA_public_encrypt(
strlen(password) + 1,
(uint8_t*)password,
(uint8_t*)result->data,
rsa,
RSA_PKCS1_OAEP_PADDING
) <= 0)
{
free(result->data);
result->size = 0;
result->data = NULL;
DEBUG_ERROR("rsa public encrypt failed");
EVP_PKEY_free(rsaKey);
BIO_free(bioKey);
return false;
}
EVP_PKEY_free(rsaKey);
BIO_free(bioKey);
return true;
#endif
#if defined(USE_NETTLE)
struct asn1_der_iterator der;
struct asn1_der_iterator j;
struct rsa_public_key pub;
if (asn1_der_iterator_first(&der, SPICE_TICKET_PUBKEY_BYTES, pub_key) == ASN1_ITERATOR_CONSTRUCTED
&& der.type == ASN1_SEQUENCE
&& asn1_der_decode_constructed_last(&der) == ASN1_ITERATOR_CONSTRUCTED
&& der.type == ASN1_SEQUENCE
&& asn1_der_decode_constructed(&der, &j) == ASN1_ITERATOR_PRIMITIVE
&& j.type == ASN1_IDENTIFIER
&& asn1_der_iterator_next(&der) == ASN1_ITERATOR_PRIMITIVE
&& der.type == ASN1_BITSTRING
&& asn1_der_decode_bitstring_last(&der))
{
if (j.length != 9)
{
DEBUG_ERROR("Invalid key, not RSA");
return false;
}
if (asn1_der_iterator_next(&j) == ASN1_ITERATOR_PRIMITIVE
&& j.type == ASN1_NULL
&& j.length == 0
&& asn1_der_iterator_next(&j) == ASN1_ITERATOR_END)
{
rsa_public_key_init(&pub);
if (!rsa_public_key_from_der_iterator(&pub, 0, &der))
{
DEBUG_ERROR("Unable to load public key from DER iterator");
rsa_public_key_clear(&pub);
return false;
}
}
}
mpz_t p;
mpz_init(p);
oaep_pad(p, pub.size, (uint8_t *)password, strlen(password)+1);
mpz_powm(p, p, pub.e, pub.n);
result->size = pub.size;
result->data = malloc(pub.size);
nettle_mpz_get_str_256(pub.size, (uint8_t *)result->data, p);
rsa_public_key_clear(&pub);
mpz_clear(p);
return true;
#endif
}
void spice_rsa_free_password(struct spice_password * pass)
{
free(pass->data);
pass->size = 0;
pass->data = NULL;
}

View File

@@ -1,6 +1,6 @@
/* /*
Looking Glass - KVM FrameRelay (KVMFR) Client Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com> Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under This program is free software; you can redistribute it and/or modify it under
@@ -17,20 +17,14 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA Place, Suite 330, Boston, MA 02111-1307 USA
*/ */
#pragma once
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h>
struct IVSHMEM struct spice_password
{ {
char * data;
unsigned int size; unsigned int size;
void * mem;
// internal use
void * opaque;
}; };
void ivshmemOptionsInit(); bool spice_rsa_encrypt_password(uint8_t * pub_key, char * password, struct spice_password * result);
bool ivshmemOpen(struct IVSHMEM * dev); void spice_rsa_free_password(struct spice_password * pass);
bool ivshmemOpenDev(struct IVSHMEM * dev, const char * shmDev);
void ivshmemClose(struct IVSHMEM * dev);

1667
client/spice/src/spice.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -28,16 +28,16 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <unistd.h> #include <unistd.h>
// forwards // forwards
static bool optRendererParse (struct Option * opt, const char * str); static bool optRendererParse (struct Option * opt, const char * str);
static StringList optRendererValues (struct Option * opt); static StringList optRendererValues (struct Option * opt);
static char * optRendererToString(struct Option * opt); static char * optRendererToString (struct Option * opt);
static bool optPosParse (struct Option * opt, const char * str); static bool optPosParse (struct Option * opt, const char * str);
static StringList optPosValues (struct Option * opt); static StringList optPosValues (struct Option * opt);
static char * optPosToString (struct Option * opt); static char * optPosToString (struct Option * opt);
static bool optSizeParse (struct Option * opt, const char * str); static bool optSizeParse (struct Option * opt, const char * str);
static StringList optSizeValues (struct Option * opt); static StringList optSizeValues (struct Option * opt);
static char * optSizeToString (struct Option * opt); static char * optSizeToString (struct Option * opt);
static char * optScancodeToString(struct Option * opt); static char * optScancodeToString (struct Option * opt);
static void doLicense(); static void doLicense();
@@ -52,6 +52,22 @@ static struct Option options[] =
.type = OPTION_TYPE_STRING, .type = OPTION_TYPE_STRING,
.value.x_string = NULL, .value.x_string = NULL,
}, },
{
.module = "app",
.name = "shmFile",
.description = "The path to the shared memory file",
.shortopt = 'f',
.type = OPTION_TYPE_STRING,
.value.x_string = "/dev/shm/looking-glass",
},
{
.module = "app",
.name = "shmSize",
.description = "Specify the size in MB of the shared memory file (0 = detect)",
.shortopt = 'L',
.type = OPTION_TYPE_INT,
.value.x_int = 0,
},
{ {
.module = "app", .module = "app",
.name = "renderer", .name = "renderer",
@@ -135,13 +151,6 @@ static struct Option options[] =
.type = OPTION_TYPE_BOOL, .type = OPTION_TYPE_BOOL,
.value.x_bool = true, .value.x_bool = true,
}, },
{
.module = "win",
.name = "forceAspect",
.description = "Force the window to maintain the aspect ratio",
.type = OPTION_TYPE_BOOL,
.value.x_bool = true,
},
{ {
.module = "win", .module = "win",
.name = "borderless", .name = "borderless",
@@ -175,8 +184,8 @@ static struct Option options[] =
}, },
{ {
.module = "win", .module = "win",
.name = "fpsMin", .name = "fpsLimit",
.description = "Frame rate minimum (0 = disable - not recommended, -1 = auto detect)", .description = "Frame rate limit (0 = disable - not recommended, -1 = auto detect)",
.shortopt = 'K', .shortopt = 'K',
.type = OPTION_TYPE_INT, .type = OPTION_TYPE_INT,
.value.x_int = -1, .value.x_int = -1,
@@ -247,13 +256,6 @@ static struct Option options[] =
.type = OPTION_TYPE_INT, .type = OPTION_TYPE_INT,
.value.x_int = 0, .value.x_int = 0,
}, },
{
.module = "input",
.name = "mouseRedraw",
.description = "Mouse movements trigger redraws (ignores FPS minimum)",
.type = OPTION_TYPE_BOOL,
.value.x_bool = true,
},
// spice options // spice options
{ {
@@ -316,13 +318,6 @@ static struct Option options[] =
.type = OPTION_TYPE_BOOL, .type = OPTION_TYPE_BOOL,
.value.x_bool = true .value.x_bool = true
}, },
{
.module = "spice",
.name = "captureOnStart",
.description = "Capture mouse and keyboard on start",
.type = OPTION_TYPE_BOOL,
.value.x_bool = false
},
{0} {0}
}; };
@@ -385,6 +380,8 @@ bool config_load(int argc, char * argv[])
} }
// setup the application params for the basic types // setup the application params for the basic types
params.shmFile = option_get_string("app", "shmFile" );
params.shmSize = option_get_int ("app", "shmSize" ) * 1048576;
params.cursorPollInterval = option_get_int ("app", "cursorPollInterval"); params.cursorPollInterval = option_get_int ("app", "cursorPollInterval");
params.framePollInterval = option_get_int ("app", "framePollInterval" ); params.framePollInterval = option_get_int ("app", "framePollInterval" );
@@ -392,11 +389,10 @@ bool config_load(int argc, char * argv[])
params.autoResize = option_get_bool ("win", "autoResize" ); params.autoResize = option_get_bool ("win", "autoResize" );
params.allowResize = option_get_bool ("win", "allowResize" ); params.allowResize = option_get_bool ("win", "allowResize" );
params.keepAspect = option_get_bool ("win", "keepAspect" ); params.keepAspect = option_get_bool ("win", "keepAspect" );
params.forceAspect = option_get_bool ("win", "forceAspect" );
params.borderless = option_get_bool ("win", "borderless" ); params.borderless = option_get_bool ("win", "borderless" );
params.fullscreen = option_get_bool ("win", "fullScreen" ); params.fullscreen = option_get_bool ("win", "fullScreen" );
params.maximize = option_get_bool ("win", "maximize" ); params.maximize = option_get_bool ("win", "maximize" );
params.fpsMin = option_get_int ("win", "fpsMin" ); params.fpsLimit = option_get_int ("win", "fpsLimit" );
params.showFPS = option_get_bool ("win", "showFPS" ); params.showFPS = option_get_bool ("win", "showFPS" );
params.ignoreQuit = option_get_bool ("win", "ignoreQuit" ); params.ignoreQuit = option_get_bool ("win", "ignoreQuit" );
params.noScreensaver = option_get_bool ("win", "noScreensaver"); params.noScreensaver = option_get_bool ("win", "noScreensaver");
@@ -406,7 +402,6 @@ bool config_load(int argc, char * argv[])
params.escapeKey = option_get_int ("input", "escapeKey" ); params.escapeKey = option_get_int ("input", "escapeKey" );
params.hideMouse = option_get_bool ("input", "hideCursor" ); params.hideMouse = option_get_bool ("input", "hideCursor" );
params.mouseSens = option_get_int ("input", "mouseSens" ); params.mouseSens = option_get_int ("input", "mouseSens" );
params.mouseRedraw = option_get_bool ("input", "mouseRedraw" );
params.minimizeOnFocusLoss = option_get_bool("win", "minimizeOnFocusLoss"); params.minimizeOnFocusLoss = option_get_bool("win", "minimizeOnFocusLoss");
@@ -428,7 +423,6 @@ bool config_load(int argc, char * argv[])
} }
params.scaleMouseInput = option_get_bool("spice", "scaleCursor"); params.scaleMouseInput = option_get_bool("spice", "scaleCursor");
params.captureOnStart = option_get_bool("spice", "captureOnStart");
} }
return true; return true;
@@ -465,9 +459,6 @@ static void doLicense()
static bool optRendererParse(struct Option * opt, const char * str) static bool optRendererParse(struct Option * opt, const char * str)
{ {
if (!str)
return false;
if (strcasecmp(str, "auto") == 0) if (strcasecmp(str, "auto") == 0)
{ {
params.forceRenderer = false; params.forceRenderer = false;
@@ -509,9 +500,6 @@ static char * optRendererToString(struct Option * opt)
static bool optPosParse(struct Option * opt, const char * str) static bool optPosParse(struct Option * opt, const char * str)
{ {
if (!str)
return false;
if (strcmp(str, "center") == 0) if (strcmp(str, "center") == 0)
{ {
params.center = true; params.center = true;
@@ -549,9 +537,6 @@ static char * optPosToString(struct Option * opt)
static bool optSizeParse(struct Option * opt, const char * str) static bool optSizeParse(struct Option * opt, const char * str)
{ {
if (!str)
return false;
if (sscanf(str, "%dx%d", &params.w, &params.h) == 2) if (sscanf(str, "%dx%d", &params.w, &params.h) == 2)
{ {
if (params.w < 1 || params.h < 1) if (params.w < 1 || params.h < 1)

View File

@@ -19,7 +19,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "ll.h" #include "ll.h"
#include "common/locking.h" #include "utils.h"
#include <stdlib.h> #include <stdlib.h>
#include <assert.h> #include <assert.h>
@@ -157,4 +157,4 @@ bool ll_walk(struct ll * list, void ** data)
LG_UNLOCK(list->lock); LG_UNLOCK(list->lock);
return true; return true;
} }

File diff suppressed because it is too large Load Diff

View File

@@ -18,41 +18,17 @@ Place, Suite 330, Boston, MA 02111-1307 USA
*/ */
#include <stdbool.h> #include <stdbool.h>
#include <stdatomic.h>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include "interface/app.h" #include "interface/app.h"
#include "dynamic/renderers.h" #include "dynamic/renderers.h"
#include "dynamic/clipboards.h" #include "dynamic/clipboards.h"
#include "common/ivshmem.h"
#include "spice/spice.h" #include "spice/spice.h"
#include <lgmp/client.h>
enum RunState
{
APP_STATE_RUNNING,
APP_STATE_RESTART,
APP_STATE_SHUTDOWN
};
struct CursorInfo
{
int x , y;
int hx, hy;
};
enum WarpState
{
WARP_STATE_ARMED,
WARP_STATE_ON,
WARP_STATE_ACTIVE,
WARP_STATE_OFF
};
struct AppState struct AppState
{ {
enum RunState state; bool running;
bool ignoreInput; bool ignoreInput;
bool escapeActive; bool escapeActive;
SDL_Scancode escapeAction; SDL_Scancode escapeAction;
@@ -63,57 +39,35 @@ struct AppState
int windowW, windowH; int windowW, windowH;
SDL_Point srcSize; SDL_Point srcSize;
LG_RendererRect dstRect; LG_RendererRect dstRect;
struct CursorInfo cursor; SDL_Point cursor;
bool cursorVisible; bool cursorVisible;
bool haveCursorPos;
bool serverMode; float scaleX, scaleY;
bool haveCursorPos; float accX, accY;
bool drawCursor;
bool cursorInView;
bool updateCursor;
bool initialCursorSync;
float scaleX, scaleY;
float accX, accY;
int curLastX;
int curLastY;
bool haveCurLocal;
int curLocalX;
int curLocalY;
bool haveAligned;
enum WarpState warpState;
int warpFromX, warpFromY;
int warpToX , warpToY;
const LG_Renderer * lgr; const LG_Renderer * lgr;
void * lgrData; void * lgrData;
bool lgrResize; bool lgrResize;
SDL_Thread * t_frame;
const LG_Clipboard * lgc; const LG_Clipboard * lgc;
SpiceDataType cbType; SpiceDataType cbType;
struct ll * cbRequestList; struct ll * cbRequestList;
SDL_SysWMinfo wminfo;
SDL_Window * window; SDL_Window * window;
int shmFD;
struct KVMFRHeader * shm;
unsigned int shmSize;
struct IVSHMEM shm; uint64_t frameTime;
PLGMPClient lgmp; uint64_t lastFrameTime;
PLGMPClientQueue frameQueue; uint64_t renderTime;
PLGMPClientQueue pointerQueue; uint64_t frameCount;
uint64_t renderCount;
atomic_uint_least64_t frameTime;
uint64_t lastFrameTime;
uint64_t renderTime;
uint64_t frameCount;
uint64_t renderCount;
uint64_t resizeTimeout;
bool resizeDone;
KeybindHandle kbFS; KeybindHandle kbFS;
KeybindHandle kbInput; KeybindHandle kbInput;
KeybindHandle kbQuit;
KeybindHandle kbMouseSensInc; KeybindHandle kbMouseSensInc;
KeybindHandle kbMouseSensDec; KeybindHandle kbMouseSensDec;
KeybindHandle kbCtrlAltFn[12]; KeybindHandle kbCtrlAltFn[12];
@@ -127,7 +81,6 @@ struct AppParams
bool autoResize; bool autoResize;
bool allowResize; bool allowResize;
bool keepAspect; bool keepAspect;
bool forceAspect;
bool borderless; bool borderless;
bool fullscreen; bool fullscreen;
bool maximize; bool maximize;
@@ -135,7 +88,9 @@ struct AppParams
bool center; bool center;
int x, y; int x, y;
unsigned int w, h; unsigned int w, h;
unsigned int fpsMin; const char * shmFile;
unsigned int shmSize;
unsigned int fpsLimit;
bool showFPS; bool showFPS;
bool useSpiceInput; bool useSpiceInput;
bool useSpiceClipboard; bool useSpiceClipboard;
@@ -150,7 +105,6 @@ struct AppParams
bool grabKeyboard; bool grabKeyboard;
SDL_Scancode escapeKey; SDL_Scancode escapeKey;
bool showAlerts; bool showAlerts;
bool captureOnStart;
unsigned int cursorPollInterval; unsigned int cursorPollInterval;
unsigned int framePollInterval; unsigned int framePollInterval;
@@ -160,7 +114,6 @@ struct AppParams
const char * windowTitle; const char * windowTitle;
int mouseSens; int mouseSens;
bool mouseRedraw;
}; };
struct CBRequest struct CBRequest
@@ -179,4 +132,4 @@ struct KeybindHandle
// forwards // forwards
extern struct AppState state; extern struct AppState state;
extern struct AppParams params; extern struct AppParams params;

View File

@@ -5,23 +5,37 @@ include_directories(
${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}/include
) )
add_definitions(-D_GNU_SOURCE)
if(ENABLE_BACKTRACE) if(ENABLE_BACKTRACE)
add_definitions(-DENABLE_BACKTRACE) add_definitions(-DENABLE_BACKTRACE)
endif() endif()
add_subdirectory(src/platform)
set(COMMON_SOURCES set(COMMON_SOURCES
src/objectlist.c
src/stringutils.c src/stringutils.c
src/stringlist.c
src/option.c src/option.c
src/framebuffer.c src/framebuffer.c
) )
add_library(lg_common STATIC ${COMMON_SOURCES}) set(LINUX_SOURCES
target_link_libraries(lg_common lg_common_platform) src/crash.linux.c
src/sysinfo.linux.c
)
set(WINDOWS_SOURCES
src/crash.windows.c
src/sysinfo.windows.c
)
if(WIN32)
set(SOURCES ${COMMON_SOURCES} ${WINDOWS_SOURCES})
add_library(lg_common STATIC ${SOURCES})
else()
set(SOURCES ${COMMON_SOURCES} ${LINUX_SOURCES})
add_library(lg_common STATIC ${SOURCES})
if(ENABLE_BACKTRACE)
target_link_libraries(lg_common bfd)
endif()
endif()
target_include_directories(lg_common target_include_directories(lg_common
INTERFACE INTERFACE

View File

@@ -20,8 +20,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <stdint.h> #include <stdint.h>
#define LGMP_Q_POINTER 1 #define KVMFR_HEADER_MAGIC "[[KVMFR]]"
#define LGMP_Q_FRAME 2 #define KVMFR_HEADER_VERSION 9
typedef enum FrameType typedef enum FrameType
{ {
@@ -34,14 +34,6 @@ typedef enum FrameType
} }
FrameType; FrameType;
enum
{
CURSOR_FLAG_POSITION = 0x1,
CURSOR_FLAG_VISIBLE = 0x2,
CURSOR_FLAG_SHAPE = 0x4
};
typedef uint32_t KVMFRCursorFlags;
typedef enum CursorType typedef enum CursorType
{ {
CURSOR_TYPE_COLOR , CURSOR_TYPE_COLOR ,
@@ -50,35 +42,49 @@ typedef enum CursorType
} }
CursorType; CursorType;
#define KVMFR_MAGIC "KVMFR---" #define KVMFR_CURSOR_FLAG_UPDATE 1 // cursor update available
#define KVMFR_VERSION 3 #define KVMFR_CURSOR_FLAG_VISIBLE 2 // cursor is visible
#define KVMFR_CURSOR_FLAG_SHAPE 4 // shape updated
typedef struct KVMFR #define KVMFR_CURSOR_FLAG_POS 8 // position updated
{
char magic[8];
uint32_t version;
char hostver[32];
}
KVMFR;
typedef struct KVMFRCursor typedef struct KVMFRCursor
{ {
volatile uint8_t flags; // KVMFR_CURSOR_FLAGS
int16_t x, y; // cursor x & y position int16_t x, y; // cursor x & y position
uint32_t version; // shape version
CursorType type; // shape buffer data type CursorType type; // shape buffer data type
int8_t hx, hy; // shape hotspot x & y
uint32_t width; // width of the shape uint32_t width; // width of the shape
uint32_t height; // height of the shape uint32_t height; // height of the shape
uint32_t pitch; // row length in bytes of the shape uint32_t pitch; // row length in bytes of the shape
uint64_t dataPos; // offset to the shape data
} }
KVMFRCursor; KVMFRCursor;
#define KVMFR_FRAME_FLAG_UPDATE 1 // frame update available
typedef struct KVMFRFrame typedef struct KVMFRFrame
{ {
FrameType type; // the frame data type volatile uint8_t flags; // KVMFR_FRAME_FLAGS
uint32_t width; // the width FrameType type; // the frame data type
uint32_t height; // the height uint32_t width; // the width
uint32_t stride; // the row stride (zero if compressed data) uint32_t height; // the height
uint32_t pitch; // the row pitch (stride in bytes or the compressed frame size) uint32_t stride; // the row stride (zero if compressed data)
uint32_t offset; // offset from the start of this header to the FrameBuffer header uint32_t pitch; // the row pitch (stride in bytes or the compressed frame size)
uint64_t dataPos; // offset to the frame
} }
KVMFRFrame; KVMFRFrame;
#define KVMFR_HEADER_FLAG_RESTART 1 // restart signal from client
#define KVMFR_HEADER_FLAG_READY 2 // ready signal from client
#define KVMFR_HEADER_FLAG_PAUSED 4 // capture has been paused by the host
typedef struct KVMFRHeader
{
char magic[sizeof(KVMFR_HEADER_MAGIC)];
uint32_t version; // version of this structure
volatile uint8_t flags; // KVMFR_HEADER_FLAGS
KVMFRFrame frame; // the frame information
KVMFRCursor cursor; // the cursor information
}
KVMFRHeader;

View File

@@ -17,13 +17,8 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA Place, Suite 330, Boston, MA 02111-1307 USA
*/ */
#ifndef __STDC_FORMAT_MACROS
#define __STDC_FORMAT_MACROS
#endif
#include <stdio.h> #include <stdio.h>
#include <inttypes.h> #include <stdlib.h>
#include "time.h"
#if defined(_WIN32) && !defined(__GNUC__) #if defined(_WIN32) && !defined(__GNUC__)
#define DIRECTORY_SEPARATOR '\\' #define DIRECTORY_SEPARATOR '\\'
@@ -53,16 +48,16 @@ Place, Suite 330, Boston, MA 02111-1307 USA
sizeof(s) > 20 && (s)[sizeof(s)-21] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 20 : \ sizeof(s) > 20 && (s)[sizeof(s)-21] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 20 : \
sizeof(s) > 21 && (s)[sizeof(s)-22] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 21 : (s)) sizeof(s) > 21 && (s)[sizeof(s)-22] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 21 : (s))
#define DEBUG_PRINT(type, fmt, ...) do {fprintf(stderr, "%12" PRId64 " " type " %20s:%-4u | %-30s | " fmt "\n", microtime(), STRIPPATH(__FILE__), __LINE__, __FUNCTION__, ##__VA_ARGS__);} while (0) #define DEBUG_PRINT(type, fmt, ...) do {fprintf(stderr, type " %20s:%-4u | %-30s | " fmt "\n", STRIPPATH(__FILE__), __LINE__, __FUNCTION__, ##__VA_ARGS__);} while (0)
#define DEBUG_BREAK() DEBUG_PRINT("[ ]", "%s", "================================================================================")
#define DEBUG_INFO(fmt, ...) DEBUG_PRINT("[I]", fmt, ##__VA_ARGS__) #define DEBUG_INFO(fmt, ...) DEBUG_PRINT("[I]", fmt, ##__VA_ARGS__)
#define DEBUG_WARN(fmt, ...) DEBUG_PRINT("[W]", fmt, ##__VA_ARGS__) #define DEBUG_WARN(fmt, ...) DEBUG_PRINT("[W]", fmt, ##__VA_ARGS__)
#define DEBUG_ERROR(fmt, ...) DEBUG_PRINT("[E]", fmt, ##__VA_ARGS__) #define DEBUG_ERROR(fmt, ...) DEBUG_PRINT("[E]", fmt, ##__VA_ARGS__)
#define DEBUG_FIXME(fmt, ...) DEBUG_PRINT("[F]", fmt, ##__VA_ARGS__) #define DEBUG_FIXME(fmt, ...) DEBUG_PRINT("[F]", fmt, ##__VA_ARGS__)
#define DEBUG_FATAL(fmt, ...) do {DEBUG_PRINT("[C]", fmt, ##__VA_ARGS__); abort();} while (0)
#if defined(DEBUG_SPICE) | defined(DEBUG_IVSHMEM) #if defined(DEBUG_SPICE) | defined(DEBUG_IVSHMEM)
#define DEBUG_PROTO(fmt, args...) DEBUG_PRINT("[P]", fmt, ##args) #define DEBUG_PROTO(fmt, args...) DEBUG_PRINT("[P]", fmt, ##args)
#else #else
#define DEBUG_PROTO(fmt, ...) do {} while(0) #define DEBUG_PROTO(fmt, ...) do {} while(0)
#endif #endif

View File

@@ -1,42 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#pragma once
#include <stdbool.h>
#include <time.h>
#define TIMEOUT_INFINITE ((unsigned int)~0)
typedef struct LGEvent LGEvent;
LGEvent * lgCreateEvent(bool autoReset, unsigned int msSpinTime);
void lgFreeEvent (LGEvent * handle);
bool lgWaitEvent (LGEvent * handle, unsigned int timeout);
bool lgWaitEvents (LGEvent * handles[], int count, bool waitAll, unsigned int timeout);
bool lgSignalEvent(LGEvent * handle);
bool lgResetEvent (LGEvent * handle);
// os specific method to wrap/convert a native event into a LGEvent
// for windows this is an event HANDLE
LGEvent * lgWrapEvent(void * handle);
// Posix specific, not implmented/possible in windows
bool lgWaitEventAbs(LGEvent * handle, struct timespec * ts);
bool lgWaitEventNS (LGEvent * handle, unsigned int timeout);

View File

@@ -23,38 +23,26 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
typedef struct stFrameBuffer FrameBuffer; typedef struct stFrameBuffer * FrameBuffer;
typedef bool (*FrameBufferReadFn)(void * opaque, const void * src, size_t size); typedef bool (*FrameBufferReadFn)(void * opaque, const void * src, size_t size);
/**
* The size of the FrameBuffer struct
*/
extern const size_t FrameBufferStructSize;
/**
* Wait for the framebuffer to fill to the specified size
*/
void framebuffer_wait(const FrameBuffer * frame, size_t size);
/** /**
* Read data from the KVMFRFrame into the dst buffer * Read data from the KVMFRFrame into the dst buffer
*/ */
bool framebuffer_read(const FrameBuffer * frame, void * dst, size_t dstpitch, bool framebuffer_read(const FrameBuffer frame, void * dst, size_t size);
size_t height, size_t width, size_t bpp, size_t pitch);
/** /**
* Read data from the KVMFRFrame using a callback * Read data from the KVMFRFrame using a callback
*/ */
bool framebuffer_read_fn(const FrameBuffer * frame, size_t height, size_t width, bool framebuffer_read_fn(const FrameBuffer frame, FrameBufferReadFn fn, size_t size, void * opaque);
size_t bpp, size_t pitch, FrameBufferReadFn fn, void * opaque);
/** /**
* Prepare the framebuffer for writing * Prepare the framebuffer for writing
*/ */
void framebuffer_prepare(FrameBuffer * frame); void framebuffer_prepare(const FrameBuffer frame);
/** /**
* Write data from the src buffer into the KVMFRFrame * Write data from the src buffer into the KVMFRFrame
*/ */
bool framebuffer_write(FrameBuffer * frame, const void * src, size_t size); bool framebuffer_write(const FrameBuffer frame, const void * src, size_t size);

View File

@@ -18,23 +18,14 @@ Place, Suite 330, Boston, MA 02111-1307 USA
*/ */
#pragma once #pragma once
#include "time.h" #if defined(__GCC__) || defined(__GNUC__)
#define INTERLOCKED_AND8 __sync_fetch_and_and
#include <stdatomic.h> #define INTERLOCKED_OR8 __sync_fetch_and_or
#define INTERLOCKED_INC(x) __sync_fetch_and_add((x), 1)
#define LG_LOCK_MODE "Atomic" #define INTERLOCKED_DEC(x) __sync_fetch_and_sub((x), 1)
typedef atomic_flag LG_Lock; #else
#define LG_LOCK_INIT(x) atomic_flag_clear(&(x)) #define INTERLOCKED_OR8 InterlockedOr8
#define LG_LOCK(x) \ #define INTERLOCKED_AND8 InterlockedAnd8
while(atomic_flag_test_and_set_explicit(&(x), memory_order_acquire)) { ; } #define INTERLOCKED_INC InterlockedIncrement
#define LG_UNLOCK(x) \ #define INTERLOCKED_DEC InterlockedDecrement
atomic_flag_clear_explicit(&(x), memory_order_release); #endif
#define LG_LOCK_FREE(x)
#define INTERLOCKED_INC(x) atomic_fetch_add((x), 1)
#define INTERLOCKED_DEC(x) atomic_fetch_sub((x), 1)
#define INTERLOCKED_SECTION(lock, x) \
LG_LOCK(lock) \
x \
LG_UNLOCK(lock)

View File

@@ -0,0 +1,35 @@
/*
KVMGFX Client - A KVM Client for VGA Passthrough
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdbool.h>
typedef struct ObjectList * ObjectList;
typedef void (*ObjectFreeFn)(void * object);
ObjectList objectlist_new (ObjectFreeFn free_fn);
void objectlist_free (ObjectList * ol);
int objectlist_push (ObjectList ol, void * object);
void * objectlist_pop (ObjectList ol);
bool objectlist_remove(ObjectList ol, unsigned int index);
unsigned int objectlist_count (ObjectList ol);
void * objectlist_at (ObjectList ol, unsigned int index);
// generic free method
void objectlist_free_item(void *object);

View File

@@ -17,12 +17,31 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA Place, Suite 330, Boston, MA 02111-1307 USA
*/ */
#include <stdbool.h> #include "objectlist.h"
typedef struct StringList * StringList; typedef ObjectList StringList;
StringList stringlist_new (bool owns_strings); inline static StringList stringlist_new(bool owns_strings)
void stringlist_free (StringList * sl); {
int stringlist_push (StringList sl, char * str); return objectlist_new(owns_strings ? objectlist_free_item : 0);
unsigned int stringlist_count(StringList sl); }
char * stringlist_at (StringList sl, unsigned int index);
inline static void stringlist_free(StringList * sl)
{
return objectlist_free(sl);
}
inline static int stringlist_push (StringList sl, char * str)
{
return objectlist_push(sl, str);
}
inline static unsigned int stringlist_count(StringList sl)
{
return objectlist_count(sl);
}
inline static char * stringlist_at(StringList sl, unsigned int index)
{
return objectlist_at(sl, index);
}

View File

@@ -18,5 +18,4 @@ Place, Suite 330, Boston, MA 02111-1307 USA
*/ */
// sprintf but with buffer allocation // sprintf but with buffer allocation
int alloc_sprintf(char ** str, const char * format, ...) int alloc_sprintf(char ** str, const char * format, ...);
__attribute__ ((format (printf, 2, 3)));

View File

@@ -18,7 +18,4 @@ Place, Suite 330, Boston, MA 02111-1307 USA
*/ */
// returns the maximum number of multisamples supported by the system // returns the maximum number of multisamples supported by the system
int sysinfo_gfx_max_multisample(); int sysinfo_gfx_max_multisample();
// returns the page size
long sysinfo_getPageSize();

View File

@@ -1,112 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#if defined(_WIN32)
#include <windows.h>
#else
#include <time.h>
#include <stdint.h>
#endif
typedef struct LGTimer LGTimer;
static inline uint64_t microtime()
{
#if defined(_WIN32)
static LARGE_INTEGER freq = { 0 };
if (!freq.QuadPart)
QueryPerformanceFrequency(&freq);
LARGE_INTEGER time;
QueryPerformanceCounter(&time);
return time.QuadPart / (freq.QuadPart / 1000000LL);
#else
struct timespec time;
clock_gettime(CLOCK_MONOTONIC, &time);
return (uint64_t)time.tv_sec * 1000000LL + time.tv_nsec / 1000LL;
#endif
}
#if !defined(_WIN32)
//FIXME: make win32 versions
static inline uint64_t nanotime()
{
struct timespec time;
clock_gettime(CLOCK_MONOTONIC_RAW, &time);
return ((uint64_t)time.tv_sec * 1000000000LL) + time.tv_nsec;
}
static inline void nsleep(uint64_t ns)
{
const struct timespec ts =
{
.tv_sec = ns / 1e9,
.tv_nsec = ns - ((ns / 1e9) * 1e9)
};
nanosleep(&ts, NULL);
}
static inline void tsDiff(struct timespec *diff, const struct timespec *left,
const struct timespec *right)
{
diff->tv_sec = left->tv_sec - right->tv_sec;
diff->tv_nsec = left->tv_nsec - right->tv_nsec;
if (diff->tv_nsec < 0)
{
--diff->tv_sec;
diff->tv_nsec += 1000000000;
}
}
static inline uint32_t __iter_div_u64_rem(uint64_t dividend, uint32_t divisor, uint64_t *remainder)
{
uint32_t ret = 0;
while (dividend >= divisor) {
/* The following asm() prevents the compiler from
optimising this loop into a modulo operation. */
asm("" : "+rm"(dividend));
dividend -= divisor;
ret++;
}
*remainder = dividend;
return ret;
}
static inline void tsAdd(struct timespec *a, uint64_t ns)
{
a->tv_sec += __iter_div_u64_rem(a->tv_nsec + ns, 1000000000L, &ns);
a->tv_nsec = ns;
}
#endif
typedef bool (*LGTimerFn)(void * udata);
bool lgCreateTimer(const unsigned int intervalMS, LGTimerFn fn,
void * udata, LGTimer ** result);
void lgTimerDestroy(LGTimer * timer);

View File

@@ -17,6 +17,7 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA Place, Suite 330, Boston, MA 02111-1307 USA
*/ */
#define _GNU_SOURCE
#include "common/crash.h" #include "common/crash.h"
#include "common/debug.h" #include "common/debug.h"
@@ -97,18 +98,10 @@ static void load_symbols()
static bool lookup_address(bfd_vma pc, const char ** filename, const char ** function, unsigned int * line, unsigned int * discriminator) static bool lookup_address(bfd_vma pc, const char ** filename, const char ** function, unsigned int * line, unsigned int * discriminator)
{ {
#ifdef bfd_get_section_flags
if ((bfd_get_section_flags(crash.fd, crash.section) & SEC_ALLOC) == 0) if ((bfd_get_section_flags(crash.fd, crash.section) & SEC_ALLOC) == 0)
#else
if ((bfd_section_flags(crash.section) & SEC_ALLOC) == 0)
#endif
return false; return false;
#ifdef bfd_get_section_size
bfd_size_type size = bfd_get_section_size(crash.section); bfd_size_type size = bfd_get_section_size(crash.section);
#else
bfd_size_type size = bfd_section_size(crash.section);
#endif
if (pc >= size) if (pc >= size)
return false; return false;
@@ -237,4 +230,4 @@ bool installCrashHandler(const char * exe)
return true; return true;
} }
#endif #endif

View File

@@ -21,115 +21,53 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "common/debug.h" #include "common/debug.h"
#include <string.h> #include <string.h>
#include <stdatomic.h> #define FB_CHUNK_SIZE 1024
#include <emmintrin.h>
#include <smmintrin.h>
#include <unistd.h>
#define FB_CHUNK_SIZE 1048576 // 1MB
#define FB_SPIN_LIMIT 10000 // 10ms
struct stFrameBuffer struct stFrameBuffer
{ {
atomic_uint_least32_t wp; uint64_t wp;
uint8_t data[0]; uint8_t data[0];
}; };
const size_t FrameBufferStructSize = sizeof(FrameBuffer); bool framebuffer_read(const FrameBuffer frame, void * dst, size_t size)
void framebuffer_wait(const FrameBuffer * frame, size_t size)
{ {
while(atomic_load_explicit(&frame->wp, memory_order_acquire) != size) {} uint8_t *d = (uint8_t*)dst;
} uint64_t rp = 0;
while(rp < size)
bool framebuffer_read(const FrameBuffer * frame, void * restrict dst,
size_t dstpitch, size_t height, size_t width, size_t bpp, size_t pitch)
{
uint8_t * restrict d = (uint8_t*)dst;
uint_least32_t rp = 0;
size_t y = 0;
const size_t linewidth = width * bpp;
const size_t blocks = linewidth / 64;
const size_t left = linewidth % 64;
while(y < height)
{ {
uint_least32_t wp;
int spinCount = 0;
/* spinlock */ /* spinlock */
wp = atomic_load_explicit(&frame->wp, memory_order_acquire); while(rp == frame->wp) { }
while(wp - rp < linewidth)
{
if (++spinCount == FB_SPIN_LIMIT)
return false;
usleep(1); /* copy what we can */
wp = atomic_load_explicit(&frame->wp, memory_order_acquire); uint64_t avail = frame->wp - rp;
} avail = avail > size ? size : avail;
_mm_mfence(); memcpy(d, frame->data + rp, avail);
__m128i * restrict s = (__m128i *)(frame->data + rp);
for(int i = 0; i < blocks; ++i)
{
__m128i *_d = (__m128i *)d;
__m128i *_s = (__m128i *)s;
__m128i v1 = _mm_stream_load_si128(_s + 0);
__m128i v2 = _mm_stream_load_si128(_s + 1);
__m128i v3 = _mm_stream_load_si128(_s + 2);
__m128i v4 = _mm_stream_load_si128(_s + 3);
_mm_storeu_si128(_d + 0, v1); rp += avail;
_mm_storeu_si128(_d + 1, v2); d += avail;
_mm_storeu_si128(_d + 2, v3); size -= avail;
_mm_storeu_si128(_d + 3, v4);
d += 64;
s += 4;
}
if (left)
{
memcpy(d, s, left);
d += left;
}
rp += pitch;
d += dstpitch - linewidth;
++y;
} }
return true; return true;
} }
bool framebuffer_read_fn(const FrameBuffer * frame, size_t height, size_t width, bool framebuffer_read_fn(const FrameBuffer frame, FrameBufferReadFn fn, size_t size, void * opaque)
size_t bpp, size_t pitch, FrameBufferReadFn fn, void * opaque)
{ {
uint_least32_t rp = 0; uint64_t rp = 0;
size_t y = 0; while(rp < size)
const size_t linewidth = width * bpp;
while(y < height)
{ {
uint_least32_t wp;
int spinCount = 0;
/* spinlock */ /* spinlock */
wp = atomic_load_explicit(&frame->wp, memory_order_acquire); while(rp == frame->wp) { }
while(wp - rp < linewidth)
{
if (++spinCount == FB_SPIN_LIMIT)
return false;
usleep(1); /* copy what we can */
wp = atomic_load_explicit(&frame->wp, memory_order_acquire); uint64_t avail = frame->wp - rp;
} avail = avail > size ? size : avail;
if (!fn(opaque, frame->data + rp, linewidth)) if (!fn(opaque, frame->data + rp, avail))
return false; return false;
rp += pitch; rp += avail;
++y; size -= avail;
} }
return true; return true;
@@ -138,49 +76,20 @@ bool framebuffer_read_fn(const FrameBuffer * frame, size_t height, size_t width,
/** /**
* Prepare the framebuffer for writing * Prepare the framebuffer for writing
*/ */
void framebuffer_prepare(FrameBuffer * frame) void framebuffer_prepare(const FrameBuffer frame)
{ {
atomic_store_explicit(&frame->wp, 0, memory_order_release); frame->wp = 0;
} }
bool framebuffer_write(FrameBuffer * frame, const void * restrict src, size_t size) bool framebuffer_write(FrameBuffer frame, const void * src, size_t size)
{ {
__m128i * restrict s = (__m128i *)src;
__m128i * restrict d = (__m128i *)frame->data;
size_t wp = 0;
_mm_mfence();
/* copy in chunks */ /* copy in chunks */
while(size > 63) while(size)
{ {
__m128i *_d = (__m128i *)d; size_t copy = size < FB_CHUNK_SIZE ? FB_CHUNK_SIZE : size;
__m128i *_s = (__m128i *)s; memcpy(frame->data + frame->wp, src, copy);
__m128i v1 = _mm_stream_load_si128(_s + 0); __sync_fetch_and_add(&frame->wp, copy);
__m128i v2 = _mm_stream_load_si128(_s + 1); size -= copy;
__m128i v3 = _mm_stream_load_si128(_s + 2);
__m128i v4 = _mm_stream_load_si128(_s + 3);
_mm_store_si128(_d + 0, v1);
_mm_store_si128(_d + 1, v2);
_mm_store_si128(_d + 2, v3);
_mm_store_si128(_d + 3, v4);
s += 4;
d += 4;
size -= 64;
wp += 64;
if (wp % FB_CHUNK_SIZE == 0)
atomic_store_explicit(&frame->wp, wp, memory_order_release);
} }
if(size)
{
memcpy(frame->data + wp, s, size);
wp += size;
}
atomic_store_explicit(&frame->wp, wp, memory_order_release);
return true; return true;
} }

112
common/src/objectlist.c Normal file
View File

@@ -0,0 +1,112 @@
/*
KVMGFX Client - A KVM Client for VGA Passthrough
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "common/objectlist.h"
#include <stdlib.h>
#include <string.h>
struct ObjectList
{
ObjectFreeFn free_fn;
unsigned int size;
unsigned int count;
void ** list;
};
ObjectList objectlist_new(ObjectFreeFn free_fn)
{
ObjectList ol = malloc(sizeof(struct ObjectList));
ol->free_fn = free_fn;
ol->size = 32;
ol->count = 0;
ol->list = malloc(sizeof(void *) * ol->size);
return ol;
}
void objectlist_free(ObjectList * ol)
{
if ((*ol)->free_fn)
for(unsigned int i = 0; i < (*ol)->count; ++i)
(*ol)->free_fn((*ol)->list[i]);
free((*ol)->list);
free((*ol));
*ol = NULL;
}
int objectlist_push(ObjectList ol, void * object)
{
if (ol->count == ol->size)
{
ol->size += 32;
ol->list = realloc(ol->list, sizeof(void *) * ol->size);
}
unsigned int index = ol->count;
ol->list[ol->count++] = object;
return index;
}
void * objectlist_pop(ObjectList ol)
{
if (ol->count == 0)
return NULL;
return ol->list[--ol->count];
}
bool objectlist_remove(ObjectList ol, unsigned int index)
{
if (index >= ol->count)
return false;
if (ol->free_fn)
ol->free_fn(ol->list[index]);
void ** newlist = malloc(sizeof(void *) * ol->size);
--ol->count;
memcpy(&newlist[0], &ol->list[0], index * sizeof(void *));
memcpy(&newlist[index], &ol->list[index + 1], (ol->count - index) * sizeof(void *));
free(ol->list);
ol->list = newlist;
return true;
}
unsigned int objectlist_count(ObjectList ol)
{
return ol->count;
}
void * objectlist_at(ObjectList ol, unsigned int index)
{
if (index >= ol->count)
return NULL;
return ol->list[index];
}
void objectlist_free_item(void *object)
{
free(object);
}

View File

@@ -367,7 +367,6 @@ bool option_load(const char * filename)
int lineno = 1; int lineno = 1;
char * module = NULL; char * module = NULL;
bool line = true; bool line = true;
bool comment = false;
bool expectLine = false; bool expectLine = false;
bool expectValue = false; bool expectValue = false;
char * name = NULL; char * name = NULL;
@@ -380,10 +379,6 @@ bool option_load(const char * filename)
for(int c = fgetc(fp); !feof(fp); c = fgetc(fp)) for(int c = fgetc(fp); !feof(fp); c = fgetc(fp))
{ {
if (comment && c != '\n')
continue;
comment = false;
switch(c) switch(c)
{ {
case '[': case '[':
@@ -482,14 +477,6 @@ bool option_load(const char * filename)
(*p)[(*len)++] = c; (*p)[(*len)++] = c;
break; break;
case ';':
if (line)
{
comment = true;
break;
}
// fallthrough
default: default:
if (expectLine) if (expectLine)
{ {
@@ -613,7 +600,7 @@ void option_print()
maxLen = alloc_sprintf( maxLen = alloc_sprintf(
&line, &line,
"%-*s | Short | %-*s | Description", "%-*s | Short | %-*s | Description",
(int)(strlen(state.groups[g].module) + state.groups[g].pad + 1), strlen(state.groups[g].module) + state.groups[g].pad + 1,
"Long", "Long",
valueLen, valueLen,
"Value" "Value"
@@ -730,4 +717,4 @@ bool option_get_bool(const char * module, const char * name)
} }
assert(o->type == OPTION_TYPE_BOOL); assert(o->type == OPTION_TYPE_BOOL);
return o->value.x_bool; return o->value.x_bool;
} }

View File

@@ -1,11 +0,0 @@
cmake_minimum_required(VERSION 3.0)
project(lg_common_platform LANGUAGES C)
if (UNIX)
add_subdirectory("linux")
elseif(WIN32)
add_subdirectory("windows")
endif()
add_library(lg_common_platform INTERFACE)
target_link_libraries(lg_common_platform INTERFACE lg_common_platform_code)

View File

@@ -1,25 +0,0 @@
cmake_minimum_required(VERSION 3.0)
project(lg_common_platform_code LANGUAGES C)
include_directories(
${PROJECT_SOURCE_DIR}/include
)
add_library(lg_common_platform_code STATIC
crash.c
sysinfo.c
thread.c
event.c
ivshmem.c
time.c
)
if(ENABLE_BACKTRACE)
target_link_libraries(lg_common_platform_code bfd)
endif()
target_link_libraries(lg_common_platform_code
lg_common
pthread
rt
)

View File

@@ -1,201 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "common/event.h"
#include "common/debug.h"
#include <stdlib.h>
#include <pthread.h>
#include <assert.h>
#include <errno.h>
#include <stdatomic.h>
#include <stdint.h>
struct LGEvent
{
pthread_mutex_t mutex;
pthread_cond_t cond;
uint32_t flag;
bool autoReset;
};
LGEvent * lgCreateEvent(bool autoReset, unsigned int msSpinTime)
{
LGEvent * handle = (LGEvent *)calloc(sizeof(LGEvent), 1);
if (!handle)
{
DEBUG_ERROR("Failed to allocate memory");
return NULL;
}
if (pthread_mutex_init(&handle->mutex, NULL) != 0)
{
DEBUG_ERROR("Failed to create the mutex");
free(handle);
return NULL;
}
if (pthread_cond_init(&handle->cond, NULL) != 0)
{
pthread_mutex_destroy(&handle->mutex);
free(handle);
return NULL;
}
handle->autoReset = autoReset;
return handle;
}
void lgFreeEvent(LGEvent * handle)
{
assert(handle);
pthread_cond_destroy (&handle->cond );
pthread_mutex_destroy(&handle->mutex);
free(handle);
}
bool lgWaitEventAbs(LGEvent * handle, struct timespec * ts)
{
assert(handle);
if (pthread_mutex_lock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to lock the mutex");
return false;
}
bool ret = true;
int res;
while(ret && !atomic_load(&handle->flag))
{
if (!ts)
{
if ((res = pthread_cond_wait(&handle->cond, &handle->mutex)) != 0)
{
DEBUG_ERROR("Failed to wait on the condition (err: %d)", res);
ret = false;
}
}
else
{
switch((res = pthread_cond_timedwait(&handle->cond, &handle->mutex, ts)))
{
case 0:
break;
case ETIMEDOUT:
ret = false;
break;
default:
ret = false;
DEBUG_ERROR("Timed wait failed (err: %d)", res);
break;
}
}
}
if (handle->autoReset)
atomic_store(&handle->flag, 0);
if (pthread_mutex_unlock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to unlock the mutex");
return false;
}
return ret;
}
bool lgWaitEventNS(LGEvent * handle, unsigned int timeout)
{
if (timeout == TIMEOUT_INFINITE)
return lgWaitEventAbs(handle, NULL);
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
uint64_t nsec = ts.tv_nsec + timeout;
if(nsec > 1e9)
{
ts.tv_nsec = nsec - 1e9;
++ts.tv_sec;
}
else
ts.tv_nsec = nsec;
return lgWaitEventAbs(handle, &ts);
}
bool lgWaitEvent(LGEvent * handle, unsigned int timeout)
{
if (timeout == TIMEOUT_INFINITE)
return lgWaitEventAbs(handle, NULL);
return lgWaitEventNS(handle, timeout * 1000000);
}
bool lgSignalEvent(LGEvent * handle)
{
assert(handle);
if (pthread_mutex_lock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to lock the mutex");
return false;
}
atomic_store(&handle->flag, 1);
if (pthread_mutex_unlock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to unlock the mutex");
return false;
}
if (pthread_cond_broadcast(&handle->cond) != 0)
{
DEBUG_ERROR("Failed to signal the condition");
return false;
}
return true;
}
bool lgResetEvent(LGEvent * handle)
{
assert(handle);
if (pthread_mutex_lock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to lock the mutex");
return false;
}
atomic_store(&handle->flag, 0);
if (pthread_mutex_unlock(&handle->mutex) != 0)
{
DEBUG_ERROR("Failed to unlock the mutex");
return false;
}
return true;
}

View File

@@ -1,267 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "common/ivshmem.h"
#include <assert.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <string.h>
#include "common/debug.h"
#include "common/option.h"
#include "common/stringutils.h"
struct IVSHMEMInfo
{
int fd;
int size;
};
static int uioOpenFile(const char * shmDevice, const char * file)
{
char * path;
alloc_sprintf(&path, "/sys/class/uio/%s/%s", shmDevice, file);
int fd = open(path, O_RDONLY);
if (fd < 0)
{
free(path);
return -1;
}
free(path);
return fd;
}
static char * uioGetName(const char * shmDevice)
{
int fd = uioOpenFile(shmDevice, "name");
if (fd < 0)
return NULL;
char * name = malloc(32);
int len = read(fd, name, 31);
if (len <= 0)
{
free(name);
close(fd);
return NULL;
}
name[len] = '\0';
close(fd);
while(len > 0 && name[len-1] == '\n')
{
--len;
name[len] = '\0';
}
return name;
}
static bool ivshmemDeviceValidator(struct Option * opt, const char ** error)
{
// if it's not a uio device, it must be a file on disk
if (strlen(opt->value.x_string) > 3 && memcmp(opt->value.x_string, "uio", 3) != 0)
{
struct stat st;
if (stat(opt->value.x_string, &st) != 0)
{
*error = "Invalid path to the ivshmem file specified";
return false;
}
return true;
}
char * name = uioGetName(opt->value.x_string);
if (!name)
{
*error = "Failed to get the uio device name";
return false;
}
if (strcmp(name, "KVMFR") != 0)
{
free(name);
*error = "Device is not a KVMFR device";
return false;
}
free(name);
return true;
}
static StringList ivshmemDeviceGetValues(struct Option * option)
{
StringList sl = stringlist_new(true);
DIR * d = opendir("/sys/class/uio");
if (!d)
return sl;
struct dirent * dir;
while((dir = readdir(d)) != NULL)
{
if (dir->d_name[0] == '.')
continue;
char * name = uioGetName(dir->d_name);
if (!name)
continue;
if (strcmp(name, "KVMFR") == 0)
{
char * devName;
alloc_sprintf(&devName, "/dev/%s", dir->d_name);
stringlist_push(sl, devName);
}
free(name);
}
closedir(d);
return sl;
}
void ivshmemOptionsInit()
{
struct Option options[] =
{
{
.module = "app",
.name = "shmFile",
.shortopt = 'f',
.description = "The path to the shared memory file, or the name of the uio device to use, ie: uio0",
.type = OPTION_TYPE_STRING,
.value.x_string = "/dev/shm/looking-glass",
.validator = ivshmemDeviceValidator,
.getValues = ivshmemDeviceGetValues
},
{0}
};
option_register(options);
}
bool ivshmemOpen(struct IVSHMEM * dev)
{
return ivshmemOpenDev(dev, option_get_string("app", "shmFile"));
}
bool ivshmemOpenDev(struct IVSHMEM * dev, const char * shmDevice)
{
assert(dev);
unsigned int devSize;
int devFD;
dev->opaque = NULL;
DEBUG_INFO("KVMFR Device : %s", shmDevice);
if (strlen(shmDevice) > 8 && memcmp(shmDevice, "/dev/uio", 8) == 0)
{
const char * uioDev = shmDevice + 5;
// get the device size
int fd = uioOpenFile(uioDev, "maps/map0/size");
if (fd < 0)
{
DEBUG_ERROR("Failed to open %s/size", uioDev);
return false;
}
char size[32];
int len = read(fd, size, sizeof(size) - 1);
if (len <= 0)
{
DEBUG_ERROR("Failed to read the device size");
close(fd);
return false;
}
size[len] = '\0';
close(fd);
devSize = strtoul(size, NULL, 16);
devFD = open(shmDevice, O_RDWR, (mode_t)0600);
if (devFD < 0)
{
DEBUG_ERROR("Failed to open: %s", shmDevice);
DEBUG_ERROR("Do you have permission to access the device?");
return false;
}
}
else
{
struct stat st;
if (stat(shmDevice, &st) != 0)
{
DEBUG_ERROR("Failed to stat: %s", shmDevice);
return false;
}
devSize = st.st_size;
devFD = open(shmDevice, O_RDWR, (mode_t)0600);
if (devFD < 0)
{
DEBUG_ERROR("Failed to open: %s", shmDevice);
return false;
}
}
void * map = mmap(0, devSize, PROT_READ | PROT_WRITE, MAP_SHARED, devFD, 0);
if (map == MAP_FAILED)
{
DEBUG_ERROR("Failed to map the shared memory device: %s", shmDevice);
return false;
}
struct IVSHMEMInfo * info =
(struct IVSHMEMInfo *)malloc(sizeof(struct IVSHMEMInfo));
info->size = devSize;
info->fd = devFD;
dev->opaque = info;
dev->size = devSize;
dev->mem = map;
return true;
}
void ivshmemClose(struct IVSHMEM * dev)
{
assert(dev);
if (!dev->opaque)
return;
struct IVSHMEMInfo * info =
(struct IVSHMEMInfo *)dev->opaque;
munmap(dev->mem, info->size);
close(info->fd);
free(info);
dev->mem = NULL;
dev->size = 0;
dev->opaque = NULL;
}

View File

@@ -1,76 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "common/thread.h"
#include <stdlib.h>
#include <pthread.h>
#include "common/debug.h"
struct LGThread
{
const char * name;
LGThreadFunction function;
void * opaque;
pthread_t handle;
int resultCode;
};
static void * threadWrapper(void * opaque)
{
LGThread * handle = (LGThread *)opaque;
handle->resultCode = handle->function(handle->opaque);
return NULL;
}
bool lgCreateThread(const char * name, LGThreadFunction function, void * opaque, LGThread ** handle)
{
*handle = (LGThread*)malloc(sizeof(LGThread));
(*handle)->name = name;
(*handle)->function = function;
(*handle)->opaque = opaque;
if (pthread_create(&(*handle)->handle, NULL, threadWrapper, *handle) != 0)
{
DEBUG_ERROR("pthread_create failed for thread: %s", name);
free(*handle);
*handle = NULL;
return false;
}
pthread_setname_np((*handle)->handle, name);
return true;
}
bool lgJoinThread(LGThread * handle, int * resultCode)
{
if (pthread_join(handle->handle, NULL) != 0)
{
DEBUG_ERROR("pthread_join failed for thread: %s", handle->name);
free(handle);
return false;
}
if (resultCode)
*resultCode = handle->resultCode;
free(handle);
return true;
}

View File

@@ -1,109 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2020-2020 Max Sistemich <maximilian.sistemich@rwth-aachen.de>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "common/time.h"
#include "common/debug.h"
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
struct LGTimer
{
LGTimerFn fn;
void * udata;
timer_t id;
bool running;
};
static void TimerProc(union sigval arg)
{
LGTimer * timer = (LGTimer *)arg.sival_ptr;
if (!timer->fn(timer->udata))
{
if (timer_delete(timer->id))
DEBUG_ERROR("failed to destroy the timer: %s", strerror(errno));
timer->running = false;
}
}
bool lgCreateTimer(const unsigned int intervalMS, LGTimerFn fn,
void * udata, LGTimer ** result)
{
LGTimer * ret = malloc(sizeof(LGTimer));
if (!ret)
{
DEBUG_ERROR("failed to malloc LGTimer struct");
return false;
}
ret->fn = fn;
ret->udata = udata;
ret->running = true;
struct sigevent sev =
{
.sigev_notify = SIGEV_THREAD,
.sigev_notify_function = &TimerProc,
.sigev_value.sival_ptr = ret,
};
if (timer_create(CLOCK_MONOTONIC, &sev, &ret->id))
{
DEBUG_ERROR("failed to create timer: %s", strerror(errno));
free(ret);
return false;
}
struct timespec interval =
{
.tv_sec = 0,
.tv_nsec = intervalMS * 1000 * 1000,
};
struct itimerspec spec =
{
.it_interval = interval,
.it_value = interval,
};
if (timer_settime(ret->id, 0, &spec, NULL))
{
DEBUG_ERROR("failed to set timer: %s", strerror(errno));
timer_delete(ret->id);
free(ret);
return false;
}
*result = ret;
return true;
}
void lgTimerDestroy(LGTimer * timer)
{
if (timer->running)
{
if (timer_delete(timer->id))
DEBUG_ERROR("failed to destroy the timer: %s", strerror(errno));
}
free(timer);
}

View File

@@ -1,21 +0,0 @@
cmake_minimum_required(VERSION 3.0)
project(lg_common_platform_code LANGUAGES C)
include_directories(
${PROJECT_TOP}/vendor/ivshmem
)
add_library(lg_common_platform_code STATIC
crash.c
sysinfo.c
thread.c
event.c
windebug.c
ivshmem.c
time.c
)
target_link_libraries(lg_common_platform_code
lg_common
setupapi
)

View File

@@ -1,221 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "common/event.h"
#include "common/windebug.h"
#include "common/time.h"
#include <windows.h>
struct LGEvent
{
volatile int lock;
bool reset;
HANDLE handle;
bool wrapped;
unsigned int msSpinTime;
volatile bool signaled;
};
LGEvent * lgCreateEvent(bool autoReset, unsigned int msSpinTime)
{
LGEvent * event = (LGEvent *)malloc(sizeof(LGEvent));
if (!event)
{
DEBUG_ERROR("out of ram");
return NULL;
}
event->lock = 0;
event->reset = autoReset;
event->handle = CreateEvent(NULL, autoReset ? FALSE : TRUE, FALSE, NULL);
event->wrapped = false;
event->msSpinTime = msSpinTime;
event->signaled = false;
if (!event->handle)
{
DEBUG_WINERROR("Failed to create the event", GetLastError());
free(event);
return NULL;
}
return event;
}
LGEvent * lgWrapEvent(void * handle)
{
LGEvent * event = (LGEvent *)malloc(sizeof(LGEvent));
if (!event)
{
DEBUG_ERROR("out of ram");
return NULL;
}
event->lock = 0;
event->reset = false;
event->handle = (HANDLE)handle;
event->wrapped = true;
event->msSpinTime = 0;
event->signaled = false;
return event;
}
void lgFreeEvent(LGEvent * event)
{
CloseHandle(event->handle);
}
bool lgWaitEvent(LGEvent * event, unsigned int timeout)
{
// wrapped events can't be enahnced
if (!event->wrapped)
{
if (event->signaled)
{
if (event->reset)
event->signaled = false;
return true;
}
if (timeout == 0)
{
bool ret = event->signaled;
if (event->reset)
event->signaled = false;
return ret;
}
if (event->msSpinTime)
{
unsigned int spinTime = event->msSpinTime;
if (timeout != TIMEOUT_INFINITE)
{
if (timeout > event->msSpinTime)
timeout -= event->msSpinTime;
else
{
spinTime -= timeout;
timeout = 0;
}
}
uint64_t now = microtime();
uint64_t end = now + spinTime * 1000;
while(!event->signaled)
{
now = microtime();
if (now >= end)
break;
}
if (event->signaled)
{
if (event->reset)
event->signaled = false;
return true;
}
}
}
const DWORD to = (timeout == TIMEOUT_INFINITE) ? INFINITE : (DWORD)timeout;
while(true)
{
switch(WaitForSingleObject(event->handle, to))
{
case WAIT_OBJECT_0:
if (!event->reset)
event->signaled = true;
return true;
case WAIT_ABANDONED:
continue;
case WAIT_TIMEOUT:
if (timeout == TIMEOUT_INFINITE)
continue;
return false;
case WAIT_FAILED:
DEBUG_WINERROR("Wait for event failed", GetLastError());
return false;
}
DEBUG_ERROR("Unknown wait event return code");
return false;
}
}
bool lgWaitEvents(LGEvent * events[], int count, bool waitAll, unsigned int timeout)
{
const DWORD to = (timeout == TIMEOUT_INFINITE) ? INFINITE : (DWORD)timeout;
HANDLE * handles = (HANDLE *)malloc(sizeof(HANDLE) * count);
for(int i = 0; i < count; ++i)
handles[i] = events[i]->handle;
while(true)
{
DWORD result = WaitForMultipleObjects(count, handles, waitAll, to);
if (result >= WAIT_OBJECT_0 && result < count)
{
// null non signaled events from the handle list
for(int i = 0; i < count; ++i)
if (i != result && !lgWaitEvent(events[i], 0))
events[i] = NULL;
free(handles);
return true;
}
if (result >= WAIT_ABANDONED_0 && result - WAIT_ABANDONED_0 < count)
continue;
switch(result)
{
case WAIT_TIMEOUT:
if (timeout == TIMEOUT_INFINITE)
continue;
free(handles);
return false;
case WAIT_FAILED:
DEBUG_WINERROR("Wait for event failed", GetLastError());
free(handles);
return false;
}
DEBUG_ERROR("Unknown wait event return code");
free(handles);
return false;
}
}
bool lgSignalEvent(LGEvent * event)
{
event->signaled = true;
return SetEvent(event->handle);
}
bool lgResetEvent(LGEvent * event)
{
event->signaled = false;
return ResetEvent(event->handle);
}

View File

@@ -1,153 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "common/ivshmem.h"
#include "common/option.h"
#include "common/windebug.h"
#include <windows.h>
#include "ivshmem.h"
#include <assert.h>
#include <setupapi.h>
#include <io.h>
struct IVSHMEMInfo
{
HANDLE handle;
};
void ivshmemOptionsInit()
{
static struct Option options[] = {
{
.module = "os",
.name = "shmDevice",
.description = "The IVSHMEM device to use",
.type = OPTION_TYPE_INT,
.value.x_int = 0
},
{0}
};
option_register(options);
}
bool ivshmemOpen(struct IVSHMEM * dev)
{
assert(dev);
HANDLE devHandle;
{
HDEVINFO devInfoSet;
PSP_DEVICE_INTERFACE_DETAIL_DATA infData = NULL;
SP_DEVICE_INTERFACE_DATA devInterfaceData = {0};
devInfoSet = SetupDiGetClassDevs(NULL, NULL, NULL, DIGCF_PRESENT | DIGCF_ALLCLASSES | DIGCF_DEVICEINTERFACE);
devInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
const int shmDevice = option_get_int("os", "shmDevice");
if (SetupDiEnumDeviceInterfaces(devInfoSet, NULL, &GUID_DEVINTERFACE_IVSHMEM, shmDevice, &devInterfaceData) == FALSE)
{
DWORD error = GetLastError();
if (error == ERROR_NO_MORE_ITEMS)
{
DEBUG_WINERROR("Unable to enumerate the device, is it attached?", error);
return false;
}
DEBUG_WINERROR("SetupDiEnumDeviceInterfaces failed", error);
return false;
}
DWORD reqSize = 0;
SetupDiGetDeviceInterfaceDetail(devInfoSet, &devInterfaceData, NULL, 0, &reqSize, NULL);
if (!reqSize)
{
DEBUG_WINERROR("SetupDiGetDeviceInterfaceDetail", GetLastError());
return false;
}
infData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)calloc(reqSize, 1);
infData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
if (!SetupDiGetDeviceInterfaceDetail(devInfoSet, &devInterfaceData, infData, reqSize, NULL, NULL))
{
free(infData);
DEBUG_WINERROR("SetupDiGetDeviceInterfaceDetail", GetLastError());
return false;
}
devHandle = CreateFile(infData->DevicePath, 0, 0, NULL, OPEN_EXISTING, 0, 0);
if (devHandle == INVALID_HANDLE_VALUE)
{
SetupDiDestroyDeviceInfoList(devInfoSet);
free(infData);
DEBUG_WINERROR("CreateFile returned INVALID_HANDLE_VALUE", GetLastError());
return false;
}
free(infData);
SetupDiDestroyDeviceInfoList(devInfoSet);
}
IVSHMEM_SIZE size;
if (!DeviceIoControl(devHandle, IOCTL_IVSHMEM_REQUEST_SIZE, NULL, 0, &size, sizeof(IVSHMEM_SIZE), NULL, NULL))
{
DEBUG_WINERROR("DeviceIoControl Failed", GetLastError());
return 0;
}
IVSHMEM_MMAP_CONFIG config = { .cacheMode = IVSHMEM_CACHE_WRITECOMBINED };
IVSHMEM_MMAP map = { 0 };
if (!DeviceIoControl(
devHandle,
IOCTL_IVSHMEM_REQUEST_MMAP,
&config, sizeof(IVSHMEM_MMAP_CONFIG),
&map , sizeof(IVSHMEM_MMAP),
NULL, NULL))
{
DEBUG_WINERROR("DeviceIoControl Failed", GetLastError());
return false;
}
struct IVSHMEMInfo * info =
(struct IVSHMEMInfo *)malloc(sizeof(struct IVSHMEMInfo));
info->handle = devHandle;
dev->opaque = info;
dev->size = (unsigned int)size;
dev->mem = map.ptr;
return true;
}
void ivshmemClose(struct IVSHMEM * dev)
{
assert(dev);
struct IVSHMEMInfo * info =
(struct IVSHMEMInfo *)dev->opaque;
if (!DeviceIoControl(info->handle, IOCTL_IVSHMEM_RELEASE_MMAP, NULL, 0, NULL, 0, NULL, NULL))
DEBUG_WINERROR("DeviceIoControl failed", GetLastError());
free(info);
}

View File

@@ -1,93 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "common/thread.h"
#include "common/debug.h"
#include "common/windebug.h"
#include <windows.h>
struct LGThread
{
const char * name;
LGThreadFunction function;
void * opaque;
HANDLE handle;
DWORD threadID;
int resultCode;
};
static DWORD WINAPI threadWrapper(LPVOID lpParameter)
{
LGThread * handle = (LGThread *)lpParameter;
handle->resultCode = handle->function(handle->opaque);
return 0;
}
bool lgCreateThread(const char * name, LGThreadFunction function, void * opaque, LGThread ** handle)
{
*handle = (LGThread *)malloc(sizeof(LGThread));
(*handle)->name = name;
(*handle)->function = function;
(*handle)->opaque = opaque;
(*handle)->handle = CreateThread(NULL, 0, threadWrapper, *handle, 0, &(*handle)->threadID);
if (!(*handle)->handle)
{
free(*handle);
*handle = NULL;
DEBUG_WINERROR("CreateThread failed", GetLastError());
return false;
}
return true;
}
bool lgJoinThread(LGThread * handle, int * resultCode)
{
while(true)
{
switch(WaitForSingleObject(handle->handle, INFINITE))
{
case WAIT_OBJECT_0:
if (resultCode)
*resultCode = handle->resultCode;
CloseHandle(handle->handle);
free(handle);
return true;
case WAIT_ABANDONED:
case WAIT_TIMEOUT:
continue;
case WAIT_FAILED:
DEBUG_WINERROR("Wait for thread failed", GetLastError());
CloseHandle(handle->handle);
free(handle);
return false;
}
break;
}
DEBUG_WINERROR("Unknown failure waiting for thread", GetLastError());
return false;
}

View File

@@ -1,72 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "common/time.h"
#include "common/debug.h"
// decared by the platform
extern HWND MessageHWND;
struct LGTimer
{
LGTimerFn fn;
void * udata;
UINT_PTR handle;
bool running;
};
static void TimerProc(HWND Arg1, UINT Arg2, UINT_PTR Arg3, DWORD Arg4)
{
LGTimer * timer = (LGTimer *)Arg3;
if (!timer->fn(timer->udata))
{
KillTimer(Arg1, timer->handle);
timer->running = false;
}
}
bool lgCreateTimer(const unsigned int intervalMS, LGTimerFn fn,
void * udata, LGTimer ** result)
{
LGTimer * ret = malloc(sizeof(LGTimer));
if (!ret)
{
DEBUG_ERROR("failed to malloc LGTimer struct");
return false;
}
ret->fn = fn;
ret->udata = udata;
ret->running = true;
ret->handle = SetTimer(MessageHWND, (UINT_PTR)ret, intervalMS, TimerProc);
*result = ret;
return true;
}
void lgTimerDestroy(LGTimer * timer)
{
if (timer->running)
{
if (!KillTimer(MessageHWND, timer->handle))
DEBUG_ERROR("failed to destroy the timer");
}
free(timer);
}

View File

@@ -1,79 +0,0 @@
/*
KVMGFX Client - A KVM Client for VGA Passthrough
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "common/stringlist.h"
#include <stdlib.h>
struct StringList
{
bool owns_strings;
unsigned int size;
unsigned int count;
char ** list;
};
StringList stringlist_new(bool owns_strings)
{
StringList sl = malloc(sizeof(struct StringList));
sl->owns_strings = owns_strings;
sl->size = 32;
sl->count = 0;
sl->list = malloc(sizeof(char *) * sl->size);
return sl;
}
void stringlist_free(StringList * sl)
{
if ((*sl)->owns_strings)
for(unsigned int i = 0; i < (*sl)->count; ++i)
free((*sl)->list[i]);
free((*sl)->list);
free((*sl));
*sl = NULL;
}
int stringlist_push (StringList sl, char * str)
{
if (sl->count == sl->size)
{
sl->size += 32;
sl->list = realloc(sl->list, sizeof(char *) * sl->size);
}
unsigned int index = sl->count;
sl->list[sl->count++] = str;
return index;
}
unsigned int stringlist_count(StringList sl)
{
return sl->count;
}
char * stringlist_at(StringList sl, unsigned int index)
{
if (index >= sl->count)
return NULL;
return sl->list[index];
}

View File

@@ -21,24 +21,26 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <stdlib.h> #include <stdlib.h>
#include <stdarg.h> #include <stdarg.h>
static int valloc_sprintf(char ** str, const char * format, va_list ap) int alloc_sprintf(char ** str, const char * format, ...)
{ {
if (!str) if (!str)
return -1; return -1;
*str = NULL; *str = NULL;
va_list ap;
va_list ap1; va_start(ap, format);
va_copy(ap1, ap); int len = vsnprintf(NULL, 0, format, ap);
int len = vsnprintf(NULL, 0, format, ap1); va_end(ap);
va_end(ap1);
if (len < 0) if (len < 0)
return len; return len;
*str = malloc(len+1); *str = malloc(len+1);
va_start(ap, format);
int ret = vsnprintf(*str, len + 1, format, ap); int ret = vsnprintf(*str, len + 1, format, ap);
va_end(ap);
if (ret < 0) if (ret < 0)
{ {
free(*str); free(*str);
@@ -46,14 +48,5 @@ static int valloc_sprintf(char ** str, const char * format, va_list ap)
return ret; return ret;
} }
return ret;
}
int alloc_sprintf(char ** str, const char * format, ...)
{
va_list ap;
va_start(ap, format);
int ret = valloc_sprintf(str, format, ap);
va_end(ap);
return ret; return ret;
} }

Some files were not shown because too many files have changed in this diff Show More