mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-02-19 09:19:57 +00:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce5c36705e | ||
|
|
7aee4eed11 | ||
|
|
416c4bbb48 | ||
|
|
76007092d5 | ||
|
|
453b8e6a4d | ||
|
|
968b313993 | ||
|
|
4dde82c646 | ||
|
|
4c424cdbdf | ||
|
|
963e7f8393 | ||
|
|
f93c918aa5 | ||
|
|
d5f409b02e | ||
|
|
75cea21cfc | ||
|
|
df2a3b6151 | ||
|
|
fad4d18973 | ||
|
|
f4ad730cc4 | ||
|
|
67ddb70932 | ||
|
|
27c3a93d15 | ||
|
|
df9798c819 | ||
|
|
1dfa0ed218 | ||
|
|
01f5238a9d | ||
|
|
c382a5acb1 | ||
|
|
5e3a46beb9 | ||
|
|
6ed4e23b80 | ||
|
|
0851ae6f14 | ||
|
|
caebddce4d | ||
|
|
01da541815 | ||
|
|
9d6bb57eff | ||
|
|
438548c427 |
12
.github/FUNDING.yml
vendored
12
.github/FUNDING.yml
vendored
@@ -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']
|
|
||||||
74
.github/issue_template.md
vendored
74
.github/issue_template.md
vendored
@@ -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
1
.gitignore
vendored
@@ -7,4 +7,3 @@ module/modules.order
|
|||||||
*.a
|
*.a
|
||||||
*.o
|
*.o
|
||||||
*.exe
|
*.exe
|
||||||
*/build
|
|
||||||
|
|||||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -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
|
|
||||||
|
|||||||
32
README.md
32
README.md
@@ -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
|
||||||
|
|||||||
1
_config.yml
Normal file
1
_config.yml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
theme: jekyll-theme-cayman
|
||||||
@@ -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
55
arbiter/src/main.c
Normal 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;
|
||||||
|
}
|
||||||
0
host/.gitignore → c-host/.gitignore
vendored
0
host/.gitignore → c-host/.gitignore
vendored
@@ -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)
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -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;
|
||||||
54
c-host/include/interface/platform.h
Normal file
54
c-host/include/interface/platform.h
Normal 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);
|
||||||
@@ -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
|
||||||
};
|
};
|
||||||
453
c-host/platform/Linux/src/platform.c
Normal file
453
c-host/platform/Linux/src/platform.c
Normal 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;
|
||||||
|
}
|
||||||
@@ -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)
|
|
||||||
@@ -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
|
||||||
|
};
|
||||||
@@ -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
|
||||||
|
};
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
@@ -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);
|
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
556
c-host/platform/Windows/src/platform.c
Normal file
556
c-host/platform/Windows/src/platform.c
Normal 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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
@@ -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
475
c-host/src/app.c
Normal 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
3
client/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
bin/
|
||||||
|
build/
|
||||||
|
*.swp
|
||||||
10
client/.vimrc
Normal file
10
client/.vimrc
Normal 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()
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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 |
|
|
||||||
|------------------------------------------------------------------------------------------------------------------|
|
|------------------------------------------------------------------------------------------------------------------|
|
||||||
|
|
||||||
|--------------------------------------------------------------------------|
|
|--------------------------------------------------------------------------|
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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);
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
@@ -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
|
||||||
};
|
};
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
@@ -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();
|
||||||
}
|
}
|
||||||
30
client/spice/CMakeLists.txt
Normal file
30
client/spice/CMakeLists.txt
Normal 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}
|
||||||
|
)
|
||||||
64
client/spice/include/spice/spice.h
Normal file
64
client/spice/include/spice/spice.h
Normal 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
146
client/spice/src/messages.h
Normal 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
227
client/spice/src/rsa.c
Normal 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;
|
||||||
|
}
|
||||||
@@ -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
1667
client/spice/src/spice.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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", ¶ms.w, ¶ms.h) == 2)
|
if (sscanf(str, "%dx%d", ¶ms.w, ¶ms.h) == 2)
|
||||||
{
|
{
|
||||||
if (params.w < 1 || params.h < 1)
|
if (params.w < 1 || params.h < 1)
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
1053
client/src/main.c
1053
client/src/main.c
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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
|
||||||
@@ -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);
|
|
||||||
@@ -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);
|
||||||
@@ -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)
|
|
||||||
35
common/include/common/objectlist.h
Normal file
35
common/include/common/objectlist.h
Normal 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);
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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)));
|
|
||||||
@@ -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();
|
|
||||||
@@ -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);
|
|
||||||
@@ -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
|
||||||
@@ -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
112
common/src/objectlist.c
Normal 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);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
@@ -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)
|
|
||||||
@@ -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
|
|
||||||
)
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
)
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -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];
|
|
||||||
}
|
|
||||||
@@ -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
Reference in New Issue
Block a user