mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-02-18 00:29:48 +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']
|
||||
71
.github/issue_template.md
vendored
71
.github/issue_template.md
vendored
@@ -1,66 +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:
|
||||
* https://forum.level1techs.com/c/software/lookingGlass/142
|
||||
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.
|
||||
|
||||
Ask for help in the Looking Glass discord server
|
||||
* https://discord.gg/52SMupxkvt
|
||||
|
||||
*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).
|
||||
Normally, this is found on the guest system at:
|
||||
|
||||
%ProgramData%\Looking Glass (host)\looking-glass-host.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 unexpectedly 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
|
||||
```
|
||||
**Reports that do no include this information will be ignored and closed**
|
||||
|
||||
141
.github/workflows/build.yml
vendored
141
.github/workflows/build.yml
vendored
@@ -1,141 +0,0 @@
|
||||
name: build
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
client:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
cc: [gcc, clang]
|
||||
build_type:
|
||||
- Release
|
||||
- Debug
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Update apt
|
||||
run: |
|
||||
sudo apt-get update
|
||||
- name: Install client dependencies
|
||||
run: |
|
||||
sudo apt-get install \
|
||||
binutils-dev \
|
||||
libsdl2-dev libsdl2-ttf-dev \
|
||||
libspice-protocol-dev nettle-dev \
|
||||
libx11-dev libxss-dev libxi-dev \
|
||||
wayland-protocols
|
||||
- name: Configure client
|
||||
env:
|
||||
CC: /usr/bin/${{ matrix.cc }}
|
||||
run: |
|
||||
mkdir client/build
|
||||
cd client/build
|
||||
cmake -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DENABLE_SDL=ON ..
|
||||
- name: Build client
|
||||
run: |
|
||||
cd client/build
|
||||
make -j$(nproc)
|
||||
|
||||
module:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build kernel module
|
||||
run: |
|
||||
cd module
|
||||
make
|
||||
|
||||
host-linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Update apt
|
||||
run: |
|
||||
sudo apt-get update
|
||||
- name: Install Linux host dependencies
|
||||
run: |
|
||||
sudo apt-get install binutils-dev libgl1-mesa-dev libxcb-xfixes0-dev
|
||||
- name: Configure Linux host
|
||||
run: |
|
||||
mkdir host/build
|
||||
cd host/build
|
||||
cmake ..
|
||||
- name: Build Linux host
|
||||
run: |
|
||||
cd host/build
|
||||
make -j$(nproc)
|
||||
|
||||
host-windows-cross:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Update apt
|
||||
run: |
|
||||
sudo apt-get update
|
||||
- name: Install Windows host cross-compile dependencies
|
||||
run: |
|
||||
sudo apt-get install gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64 nsis
|
||||
- name: Configure Windows host for cross-compile
|
||||
run: |
|
||||
mkdir host/build
|
||||
cd host/build
|
||||
cmake -DCMAKE_TOOLCHAIN_FILE=../toolchain-mingw64.cmake ..
|
||||
- name: Cross-compile Windows host
|
||||
run: |
|
||||
cd host/build
|
||||
make -j$(nproc)
|
||||
- name: Build Windows host installer
|
||||
run: |
|
||||
cd host/build
|
||||
makensis platform/Windows/installer.nsi
|
||||
|
||||
host-windows-native:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Configure Windows host for native MinGW-w64
|
||||
run: |
|
||||
mkdir host\build
|
||||
cd host\build
|
||||
cmake -G "MinGW Makefiles" ..
|
||||
- name: Build Windows host on native MinGW-w64
|
||||
run: |
|
||||
cd host\build
|
||||
mingw32-make "-j$([Environment]::ProcessorCount)"
|
||||
- name: Build Windows host installer
|
||||
run: |
|
||||
cd host\build
|
||||
makensis platform\Windows\installer.nsi
|
||||
|
||||
obs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
cc: [gcc, clang]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Update apt
|
||||
run: |
|
||||
sudo apt-get update
|
||||
- name: Install obs plugin dependencies
|
||||
run: |
|
||||
sudo apt-get install binutils-dev libobs-dev libgl1-mesa-dev
|
||||
- name: Configure obs plugin
|
||||
run: |
|
||||
mkdir obs/build
|
||||
cd obs/build
|
||||
CC=/usr/bin/${{ matrix.cc }} cmake ..
|
||||
- name: Build obs plugin
|
||||
run: |
|
||||
cd obs/build
|
||||
make -j$(nproc)
|
||||
18
.github/workflows/pr-check.yml
vendored
18
.github/workflows/pr-check.yml
vendored
@@ -1,18 +0,0 @@
|
||||
name: pr-check
|
||||
on: pull_request
|
||||
jobs:
|
||||
authors:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Check AUTHORS file
|
||||
run: |
|
||||
user="$(curl -H 'Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' -s https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }} | jq -r .user.login)"
|
||||
echo "Checking if GitHub user $user is in AUTHORS file..."
|
||||
if grep -q -E '> \('"$user"'\)' AUTHORS; then
|
||||
echo "$user found in AUTHORS file, all good!"
|
||||
else
|
||||
echo "$user not found in AUTHORS file."
|
||||
echo "Please add yourself to the AUTHORS file and try again."
|
||||
exit 1
|
||||
fi
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,4 +7,3 @@ module/modules.order
|
||||
*.a
|
||||
*.o
|
||||
*.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
|
||||
|
||||
55
AUTHORS
55
AUTHORS
@@ -1,55 +0,0 @@
|
||||
The following authors have licensed all their contributions to
|
||||
the Looking Glass project under the terms detailed in LICENSE:
|
||||
|
||||
Geoffrey McRae <geoff@hostfission.com> (gnif)
|
||||
Guanzhong Chen <quantum2048@gmail.com> (quantum5)
|
||||
Tudor Brindus <me@tbrindus.ca> (Xyene)
|
||||
Jonathan Rubenstein <jrubcop@gmail.com> (JJRcop)
|
||||
arcnmx <arcnmx@users.noreply.github.com> (arcnmx)
|
||||
TheCakeIsNaOH <TheCakeIsNaOH@gmail.com> (TheCakeIsNaOH)
|
||||
NamoDev <namodev@users.noreply.github.com> (NamoDev)
|
||||
feltcat <58396817+feltcat@users.noreply.github.com> (feltcat)
|
||||
Ali Abdel-Qader <abdelqaderali@protonmail.com>
|
||||
Jack Karamanian <karamanian.jack@gmail.com>
|
||||
Mikko Rasa <tdb@tdb.fi> (DataBeaver)
|
||||
Omar Pakker <Omar007@users.noreply.github.com> (Omar007)
|
||||
Yvan da Silva <yvan.m.silva@gmail.com> (YvanDaSilva)
|
||||
r4m0n <thiagoramon@gmail.com> (r4m0n)
|
||||
Łukasz Kostka <lukasz.kostka@netng.pl>
|
||||
A.J. Ruckman <aj@ruckman.dev> (ajruckman)
|
||||
Aaron <mcd1992@users.noreply.github.com> (mcd1992)
|
||||
Alam Arias <Alam.GBC@gmail.com> (alama)
|
||||
Alexander Olofsson <ace@haxalot.com> (ananace)
|
||||
Andrew Sheldon <asheldon55@gmail.com> (aqxa1)
|
||||
Andy Chun <andy@lolc.at> (noneucat)
|
||||
Arti Zirk <arti.zirk@gmail.com>
|
||||
Ash <5615358+ash-hat@users.noreply.github.com> (ash-hat)
|
||||
Dominik Csapak <d.csapak@proxmox.com> (flumm)
|
||||
Frediano Ziglio <fziglio@redhat.com>
|
||||
Luke Brown <axios350@gmail.com> (Dynamic-Gravity)
|
||||
Marius Barbu <msb@avengis.com> (mbarbu)
|
||||
Max Sistemich <maximilian.sistemich@rwth-aachen.de> (mafrasi2)
|
||||
Michael Golisch <mgolisch@googlemail.com> (mgolisch)
|
||||
Michał Zając <michal.zajac@gmail.com>
|
||||
Netboy3 <1472804+netboy3@users.noreply.github.com> (netboy3)
|
||||
Patrick Steinhardt <ps@pks.im>
|
||||
Paul Götzinger <paul70079@gmail.com> (pagdot)
|
||||
Rikard Falkeborn <rikard.falkeborn@gmail.com> (rikardfalkeborn)
|
||||
Rokas Kupstys <rokups@zoho.com> (rokups)
|
||||
Samuel Bowman <SamuelBowman@users.noreply.github.com> (SamuelBowman)
|
||||
Txanton <txb2@live.com> (tbejos)
|
||||
Tyler Watson <tyler@tw.id.au>
|
||||
Xiretza <xiretza@xiretza.xyz> (Xiretza)
|
||||
aspen <aspenuwu@protonmail.com> (aspenluxxxy)
|
||||
camr0 <CAMR0@protonmail.com> (camr0)
|
||||
chrsm <github.personal@c.chrsm.org> (chrsm)
|
||||
commander kotori <cmdrkotori@gmail.com> (cmdrkotori)
|
||||
eater <=@eater.me> (the-eater)
|
||||
fishery <i@ovo.so> (fisherwise)
|
||||
four0four <galen@shellspace.net> (four0four)
|
||||
jmossman <jmossman@users.noreply.github.com> (jmossman)
|
||||
jonpas <jonpas33@gmail.com> (jonpas)
|
||||
orcephrye <drakethebanditi@yahoo.com> (orcephrye)
|
||||
thejavascriptman <thejavascriptman@outlook.com> (thejavascriptman)
|
||||
vroad <396351+vroad@users.noreply.github.com> (vroad)
|
||||
williamvds <w.vigolodasilva@gmail.com> (williamvds)
|
||||
54
README.md
54
README.md
@@ -1,22 +1,52 @@
|
||||
# Looking Glass
|
||||
|
||||
An extremely low latency KVMFR (KVM FrameRelay) implementation for guests with
|
||||
VGA PCI Passthrough.
|
||||
An extremely low latency KVMFR (KVM FrameRelay) implementation for guests with VGA PCI Passthrough.
|
||||
|
||||
* Project Website: https://looking-glass.io
|
||||
* Documentation: https://looking-glass.io/docs
|
||||
* Project Website: https://looking-glass.hostfission.com
|
||||
|
||||
## Donations
|
||||
|
||||
I (Geoffrey McRae) am the primary developer behind this project and I have 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.
|
||||
|
||||
* [Ko-Fi](https://ko-fi.com/lookingglass)
|
||||
* [Patreon](https://www.patreon.com/gnif)
|
||||
* [Paypal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=ESQ72XUPGKXRY)
|
||||
* BTC - 14ZFcYjsKPiVreHqcaekvHGL846u3ZuT13
|
||||
|
||||
## Documentation
|
||||
|
||||
❕❕❕ **IMPORTANT** ❕❕❕
|
||||
Please also be sure to see the following files for more information
|
||||
|
||||
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:
|
||||
* [client/README.md](client/README.md)
|
||||
* [c-host/README.md](c-host/README.md)
|
||||
* [module/README.md](module/README.md)
|
||||
|
||||
https://looking-glass.io/downloads
|
||||
## Obtaining and using Looking Glass
|
||||
|
||||
Source code for the documentation can be found in the `/doc` directory.
|
||||
Please see https://looking-glass.hostfission.com/quickstart
|
||||
|
||||
You may view this locally as HTML by running `make html` with `python3-sphinx`
|
||||
installed.
|
||||
## Latest Version
|
||||
|
||||
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.
|
||||
Latest bleeding edge builds of the Windows host application can be obtained from:
|
||||
|
||||
https://looking-glass.hostfission.com/downloads
|
||||
|
||||
# Help and support
|
||||
|
||||
## Web
|
||||
|
||||
https://forum.level1techs.com/t/looking-glass-triage/130952
|
||||
|
||||
## Discord
|
||||
|
||||
https://discord.gg/4ahCn4c
|
||||
|
||||
## IRC
|
||||
|
||||
Join us in the #LookingGlass channel on the FreeNode network
|
||||
|
||||
## Trello
|
||||
|
||||
* https://trello.com/b/tI1Xbwsg/looking-glass
|
||||
|
||||
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)
|
||||
project(profiler-client C)
|
||||
project(looking-glass-arbiter C)
|
||||
|
||||
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/")
|
||||
|
||||
@@ -29,38 +29,34 @@ set(EXE_FLAGS "-Wl,--gc-sections")
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
|
||||
execute_process(
|
||||
COMMAND cat ../../VERSION
|
||||
COMMAND cat ../VERSION
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE BUILD_VERSION
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
|
||||
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(
|
||||
${PROJECT_SOURCE_DIR}/include
|
||||
${CMAKE_BINARY_DIR}/include
|
||||
)
|
||||
|
||||
link_libraries(
|
||||
rt
|
||||
m
|
||||
)
|
||||
|
||||
set(SOURCES
|
||||
src/main.c
|
||||
)
|
||||
|
||||
add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common")
|
||||
add_subdirectory("${PROJECT_TOP}/repos/LGMP/lgmp" "${CMAKE_BINARY_DIR}/lgmp" )
|
||||
add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common")
|
||||
add_subdirectory("${PROJECT_TOP}/porthole" "${CMAKE_BINARY_DIR}/porthole")
|
||||
|
||||
add_executable(profiler-client ${SOURCES})
|
||||
target_compile_options(profiler-client PUBLIC ${PKGCONFIG_CFLAGS_OTHER})
|
||||
target_link_libraries(profiler-client
|
||||
add_executable(looking-glass-arbiter ${SOURCES})
|
||||
target_link_libraries(looking-glass-arbiter
|
||||
${EXE_FLAGS}
|
||||
lg_common
|
||||
lgmp
|
||||
porthole
|
||||
)
|
||||
|
||||
install(PROGRAMS ${CMAKE_BINARY_DIR}/looking-glass-arbiter DESTINATION bin/ COMPONENT binary)
|
||||
|
||||
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
@@ -1,26 +1,17 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(looking-glass-host C)
|
||||
|
||||
set(CMAKE_C_FLAGS_RELWITHDEBINFO "-O3 -g -DNDEBUG" CACHE STRING "compiler flags" FORCE)
|
||||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O3 -g -DNDEBUG" CACHE STRING "compiler flags" FORCE)
|
||||
|
||||
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/")
|
||||
|
||||
include(GNUInstallDirs)
|
||||
include(CheckCCompilerFlag)
|
||||
include(FeatureSummary)
|
||||
|
||||
option(OPTIMIZE_FOR_NATIVE "Build with -march=native" OFF)
|
||||
option(OPTIMIZE_FOR_NATIVE "Build with -march=native" ON)
|
||||
if(OPTIMIZE_FOR_NATIVE)
|
||||
CHECK_C_COMPILER_FLAG("-march=native" COMPILER_SUPPORTS_MARCH_NATIVE)
|
||||
if(COMPILER_SUPPORTS_MARCH_NATIVE)
|
||||
add_compile_options("-march=native")
|
||||
endif()
|
||||
else()
|
||||
CHECK_C_COMPILER_FLAG("-march=nehalem" COMPILER_SUPPORTS_MARCH_NEHALEM)
|
||||
if(COMPILER_SUPPORTS_MARCH_NEHALEM)
|
||||
add_compile_options("-march=nehalem")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
option(ENABLE_BACKTRACE "Enable backtrace support on crash" ON)
|
||||
@@ -37,18 +28,20 @@ add_compile_options(
|
||||
)
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
|
||||
get_filename_component(PROJECT_TOP "${PROJECT_SOURCE_DIR}/.." ABSOLUTE)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_BINARY_DIR}/version.c
|
||||
${CMAKE_BINARY_DIR}/_version.c
|
||||
COMMAND ${CMAKE_COMMAND} -D PROJECT_TOP=${PROJECT_TOP} -P
|
||||
${PROJECT_TOP}/version.cmake
|
||||
execute_process(
|
||||
COMMAND cat ../VERSION
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE BUILD_VERSION
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
|
||||
add_definitions(-D BUILD_VERSION='"${BUILD_VERSION}"')
|
||||
get_filename_component(PROJECT_TOP "${PROJECT_SOURCE_DIR}/.." ABSOLUTE)
|
||||
|
||||
include_directories(
|
||||
${PROJECT_SOURCE_DIR}/include
|
||||
${CMAKE_BINARY_DIR}/include
|
||||
${PROJECT_TOP}/vendor/ivshmem
|
||||
${PKGCONFIG_INCLUDE_DIRS}
|
||||
${GMP_INCLUDE_DIR}
|
||||
)
|
||||
@@ -57,12 +50,10 @@ include_directories(
|
||||
#)
|
||||
|
||||
set(SOURCES
|
||||
${CMAKE_BINARY_DIR}/version.c
|
||||
src/app.c
|
||||
)
|
||||
|
||||
add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common")
|
||||
add_subdirectory("${PROJECT_TOP}/repos/LGMP/lgmp" "${CMAKE_BINARY_DIR}/lgmp" )
|
||||
add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common")
|
||||
add_subdirectory(platform)
|
||||
|
||||
if(WIN32)
|
||||
@@ -73,17 +64,8 @@ endif()
|
||||
target_link_libraries(looking-glass-host
|
||||
lg_common
|
||||
platform
|
||||
lgmp
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
set_target_properties(looking-glass-host PROPERTIES LINK_FLAGS "-Wl,--gc-sections -Wl,--nxcompat")
|
||||
else()
|
||||
set_target_properties(looking-glass-host PROPERTIES LINK_FLAGS "-Wl,--gc-sections -z noexecstack")
|
||||
endif()
|
||||
|
||||
install(TARGETS looking-glass-host
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
COMPONENT binary)
|
||||
set_target_properties(looking-glass-host PROPERTIES LINK_FLAGS "-Wl,--gc-sections")
|
||||
install(PROGRAMS ${CMAKE_BINARY_DIR}/looking-glass-host DESTINATION bin/ COMPONENT binary)
|
||||
|
||||
feature_summary(WHAT ENABLED_FEATURES DISABLED_FEATURES)
|
||||
102
c-host/README.md
Normal file
102
c-host/README.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# General Questions
|
||||
|
||||
## What is this?
|
||||
|
||||
This is a rewrite of the host application in pure C using the MinGW toolchain.
|
||||
|
||||
## Why make this?
|
||||
|
||||
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?
|
||||
|
||||
#### For Windows on Windows
|
||||
|
||||
```
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -G "MSYS Makefiles" ..
|
||||
make
|
||||
```
|
||||
|
||||
#### For Linux on Linux
|
||||
|
||||
```
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
make
|
||||
```
|
||||
|
||||
#### For Windows cross compiling on Linux
|
||||
|
||||
```
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_TOOLCHAIN_FILE=../toolchain-mingw64.cmake ..
|
||||
make
|
||||
```
|
||||
|
||||
## Where is the log?
|
||||
|
||||
It is in your user's temp directory:
|
||||
|
||||
%TEMP%\looking-glass-host.txt
|
||||
|
||||
For example:
|
||||
|
||||
C:\Users\YourUser\AppData\Local\Temp\looking-glass-host.txt
|
||||
|
||||
You can also open it by simply right clicking the tray icon and selecting "Open Log File"
|
||||
|
||||
## Why does this version require Administrator privileges
|
||||
|
||||
This is intentional for several reasons.
|
||||
|
||||
1. NvFBC requires a system wide hook to correctly obtain the cursor position as NVIDIA decided to not provide this as part of the cursor updates.
|
||||
2. NvFBC requires administrator level access to enable the interface in the first place. (WIP)
|
||||
3. DXGI performance can be improved if we have this. (WIP)
|
||||
|
||||
## NvFBC (NVIDIA Frame Buffer Capture)
|
||||
|
||||
### Why isn't there a build with NvFBC support available.
|
||||
|
||||
~~Because NVIDIA have decided to put restrictions on the NvFBC API that simply make it incompatible with the GPL/2 licence. Providing a pre-built binary with NvFBC support would violate the EULA I have agreed to in order to access the NVidia Capture SDK.~~
|
||||
|
||||
Either I miss-read the License Agreement or it has been updated, it is now viable to produce a "derived work" from the capture SDK.
|
||||
|
||||
> 1.1 License Grant. Subject to the terms of this Agreement, NVIDIA hereby grants you a nonexclusive, non-transferable, worldwide,
|
||||
revocable, limited, royalty-free, fully paid-up license during the term of this Agreement to:
|
||||
> (i) install, use and reproduce the Licensed Software delivered by NVIDIA plus make modifications and create derivative
|
||||
works of the source code and header files delivered by NVIDIA, provided that the software is executed only in hardware products as
|
||||
specified by NVIDIA in the accompanying documentation (such as release notes) as supported, to develop, test and service your
|
||||
products (each, a “Customer Product”) that are interoperable with supported hardware products. If the NVIDIA documentation is
|
||||
silent, the supported hardware consists of certain NVIDIA GPUs; and
|
||||
|
||||
To be safe we are still not including the NVIDIA headers in the repository, but I am now providing pre-built binaries with NvFBC support included.
|
||||
|
||||
See: https://looking-glass.hostfission.com/downloads
|
||||
|
||||
### Why can't I compile NvFBC support into the host
|
||||
|
||||
You must download and install the NVidia Capture SDK. Please note that by doing so you will be agreeing to NVIDIA's SDK License agreement.
|
||||
|
||||
_-Geoff_
|
||||
91
c-host/include/interface/capture.h
Normal file
91
c-host/include/interface/capture.h
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
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>
|
||||
#include <stdint.h>
|
||||
#include "common/framebuffer.h"
|
||||
|
||||
typedef enum CaptureResult
|
||||
{
|
||||
CAPTURE_RESULT_OK ,
|
||||
CAPTURE_RESULT_REINIT ,
|
||||
CAPTURE_RESULT_TIMEOUT,
|
||||
CAPTURE_RESULT_ERROR
|
||||
}
|
||||
CaptureResult;
|
||||
|
||||
typedef enum CaptureFormat
|
||||
{
|
||||
// frame formats
|
||||
CAPTURE_FMT_BGRA ,
|
||||
CAPTURE_FMT_RGBA ,
|
||||
CAPTURE_FMT_RGBA10,
|
||||
CAPTURE_FMT_YUV420,
|
||||
|
||||
// pointer formats
|
||||
CAPTURE_FMT_COLOR ,
|
||||
CAPTURE_FMT_MONO ,
|
||||
CAPTURE_FMT_MASKED,
|
||||
|
||||
CAPTURE_FMT_MAX
|
||||
}
|
||||
CaptureFormat;
|
||||
|
||||
typedef struct CaptureFrame
|
||||
{
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
unsigned int pitch;
|
||||
unsigned int stride;
|
||||
CaptureFormat format;
|
||||
}
|
||||
CaptureFrame;
|
||||
|
||||
typedef struct CapturePointer
|
||||
{
|
||||
int x, y;
|
||||
bool visible;
|
||||
|
||||
bool shapeUpdate;
|
||||
CaptureFormat format;
|
||||
unsigned int width, height;
|
||||
unsigned int pitch;
|
||||
}
|
||||
CapturePointer;
|
||||
|
||||
typedef struct CaptureInterface
|
||||
{
|
||||
const char * (*getName )();
|
||||
void (*initOptions )();
|
||||
|
||||
bool (*create )();
|
||||
bool (*init )(void * pointerShape, const unsigned int pointerSize);
|
||||
void (*stop )();
|
||||
bool (*deinit )();
|
||||
void (*free )();
|
||||
unsigned int (*getMaxFrameSize)();
|
||||
|
||||
CaptureResult (*capture )();
|
||||
CaptureResult (*waitFrame )(CaptureFrame * frame );
|
||||
CaptureResult (*getFrame )(FrameBuffer frame );
|
||||
CaptureResult (*getPointer)(CapturePointer * pointer);
|
||||
}
|
||||
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);
|
||||
@@ -12,11 +12,8 @@ add_library(platform_Linux STATIC
|
||||
add_subdirectory("capture")
|
||||
|
||||
target_link_libraries(platform_Linux
|
||||
m
|
||||
capture
|
||||
pthread
|
||||
X11
|
||||
GL
|
||||
)
|
||||
|
||||
target_include_directories(platform_Linux
|
||||
@@ -9,7 +9,7 @@ target_link_libraries(capture_XCB
|
||||
lg_common
|
||||
xcb
|
||||
xcb-shm
|
||||
xcb-xfixes
|
||||
Xfixes
|
||||
)
|
||||
|
||||
target_include_directories(capture_XCB
|
||||
@@ -1,27 +1,25 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
/*
|
||||
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/capture.h"
|
||||
#include "interface/platform.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/event.h"
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
@@ -39,7 +37,7 @@ struct xcb
|
||||
uint32_t seg;
|
||||
int shmID;
|
||||
void * data;
|
||||
LGEvent * frameEvent;
|
||||
osEventHandle * frameEvent;
|
||||
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
@@ -54,21 +52,22 @@ struct xcb * this = NULL;
|
||||
// forwards
|
||||
|
||||
static bool xcb_deinit();
|
||||
static unsigned int xcb_getMaxFrameSize();
|
||||
|
||||
// implementation
|
||||
|
||||
static const char * xcb_getName(void)
|
||||
static const char * xcb_getName()
|
||||
{
|
||||
return "XCB";
|
||||
}
|
||||
|
||||
static bool xcb_create(CaptureGetPointerBuffer getPointerBufferFn, CapturePostPointerBuffer postPointerBufferFn)
|
||||
static bool xcb_create()
|
||||
{
|
||||
assert(!this);
|
||||
this = (struct xcb *)calloc(sizeof(struct xcb), 1);
|
||||
this->shmID = -1;
|
||||
this->data = (void *)-1;
|
||||
this->frameEvent = lgCreateEvent(true, 20);
|
||||
this->frameEvent = os_createEvent(true);
|
||||
|
||||
if (!this->frameEvent)
|
||||
{
|
||||
@@ -80,12 +79,12 @@ static bool xcb_create(CaptureGetPointerBuffer getPointerBufferFn, CapturePostPo
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool xcb_init(void)
|
||||
static bool xcb_init()
|
||||
{
|
||||
assert(this);
|
||||
assert(!this->initialized);
|
||||
|
||||
lgResetEvent(this->frameEvent);
|
||||
os_resetEvent(this->frameEvent);
|
||||
|
||||
this->xcb = xcb_connect(NULL, NULL);
|
||||
if (!this->xcb || xcb_connection_has_error(this->xcb))
|
||||
@@ -108,8 +107,7 @@ static bool xcb_init(void)
|
||||
DEBUG_INFO("Frame Size : %u x %u", this->width, this->height);
|
||||
|
||||
this->seg = xcb_generate_id(this->xcb);
|
||||
const size_t maxFrameSize = this->width * this->height * 4;
|
||||
this->shmID = shmget(IPC_PRIVATE, maxFrameSize, IPC_CREAT | 0777);
|
||||
this->shmID = shmget(IPC_PRIVATE, xcb_getMaxFrameSize(), IPC_CREAT | 0777);
|
||||
if (this->shmID == -1)
|
||||
{
|
||||
DEBUG_ERROR("shmget failed");
|
||||
@@ -132,7 +130,7 @@ fail:
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool xcb_deinit(void)
|
||||
static bool xcb_deinit()
|
||||
{
|
||||
assert(this);
|
||||
|
||||
@@ -158,19 +156,19 @@ static bool xcb_deinit(void)
|
||||
return false;
|
||||
}
|
||||
|
||||
static void xcb_free(void)
|
||||
static void xcb_free()
|
||||
{
|
||||
lgFreeEvent(this->frameEvent);
|
||||
os_freeEvent(this->frameEvent);
|
||||
free(this);
|
||||
this = NULL;
|
||||
}
|
||||
|
||||
static unsigned int xcb_getMouseScale(void)
|
||||
static unsigned int xcb_getMaxFrameSize()
|
||||
{
|
||||
return 100;
|
||||
return this->width * this->height * 4;
|
||||
}
|
||||
|
||||
static CaptureResult xcb_capture(void)
|
||||
static CaptureResult xcb_capture()
|
||||
{
|
||||
assert(this);
|
||||
assert(this->initialized);
|
||||
@@ -189,34 +187,20 @@ static CaptureResult xcb_capture(void)
|
||||
0);
|
||||
|
||||
this->hasFrame = true;
|
||||
lgSignalEvent(this->frameEvent);
|
||||
os_signalEvent(this->frameEvent);
|
||||
}
|
||||
|
||||
return CAPTURE_RESULT_OK;
|
||||
}
|
||||
|
||||
static CaptureResult xcb_waitFrame(CaptureFrame * frame,
|
||||
const size_t maxFrameSize)
|
||||
{
|
||||
lgWaitEvent(this->frameEvent, TIMEOUT_INFINITE);
|
||||
|
||||
const unsigned int maxHeight = maxFrameSize / (this->width * 4);
|
||||
|
||||
frame->width = this->width;
|
||||
frame->height = maxHeight > this->height ? this->height : maxHeight;
|
||||
frame->realHeight = this->height;
|
||||
frame->pitch = this->width * 4;
|
||||
frame->stride = this->width;
|
||||
frame->format = CAPTURE_FMT_BGRA;
|
||||
frame->rotation = CAPTURE_ROT_0;
|
||||
|
||||
return CAPTURE_RESULT_OK;
|
||||
}
|
||||
|
||||
static CaptureResult xcb_getFrame(FrameBuffer * frame, const unsigned int height)
|
||||
static CaptureResult xcb_getFrame(CaptureFrame * frame)
|
||||
{
|
||||
assert(this);
|
||||
assert(this->initialized);
|
||||
assert(frame);
|
||||
assert(frame->data);
|
||||
|
||||
os_waitEvent(this->frameEvent, TIMEOUT_INFINITE);
|
||||
|
||||
xcb_shm_get_image_reply_t * img;
|
||||
img = xcb_shm_get_image_reply(this->xcb, this->imgC, NULL);
|
||||
@@ -226,24 +210,33 @@ static CaptureResult xcb_getFrame(FrameBuffer * frame, const unsigned int height
|
||||
return CAPTURE_RESULT_ERROR;
|
||||
}
|
||||
|
||||
framebuffer_write(frame, this->data, this->width * 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);
|
||||
|
||||
this->hasFrame = false;
|
||||
return CAPTURE_RESULT_OK;
|
||||
}
|
||||
|
||||
static CaptureResult xcb_getPointer(CapturePointer * pointer)
|
||||
{
|
||||
memset(pointer, 0, sizeof(CapturePointer));
|
||||
return CAPTURE_RESULT_OK;
|
||||
}
|
||||
|
||||
struct CaptureInterface Capture_XCB =
|
||||
{
|
||||
.shortName = "XCB",
|
||||
.asyncCapture = true,
|
||||
.getName = xcb_getName,
|
||||
.create = xcb_create,
|
||||
.init = xcb_init,
|
||||
.deinit = xcb_deinit,
|
||||
.free = xcb_free,
|
||||
.getMouseScale = xcb_getMouseScale,
|
||||
.getMaxFrameSize = xcb_getMaxFrameSize,
|
||||
.capture = xcb_capture,
|
||||
.waitFrame = xcb_waitFrame,
|
||||
.getFrame = xcb_getFrame
|
||||
};
|
||||
.getFrame = xcb_getFrame,
|
||||
.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,15 +7,10 @@ include_directories(
|
||||
|
||||
add_library(platform_Windows STATIC
|
||||
src/platform.c
|
||||
src/service.c
|
||||
src/windebug.c
|
||||
src/mousehook.c
|
||||
src/force_compose.c
|
||||
src/delay.c
|
||||
)
|
||||
|
||||
# allow use of functions for Windows Vista or later
|
||||
add_definitions(-D _WIN32_WINNT=0x6000)
|
||||
|
||||
add_subdirectory("capture")
|
||||
|
||||
FIND_PROGRAM(WINDRES_EXECUTABLE NAMES "x86_64-w64-mingw32-windres" "windres.exe" DOC "windres executable")
|
||||
@@ -29,20 +24,10 @@ target_link_libraries(platform_Windows
|
||||
"${PROJECT_BINARY_DIR}/resource.o"
|
||||
lg_common
|
||||
capture
|
||||
|
||||
userenv
|
||||
wtsapi32
|
||||
psapi
|
||||
shlwapi
|
||||
powrprof
|
||||
setupapi
|
||||
)
|
||||
|
||||
target_include_directories(platform_Windows
|
||||
PRIVATE
|
||||
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}/LICENSE" "${PROJECT_BINARY_DIR}/LICENSE.txt" COPYONLY)
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity version="1.0.0.0" processorArchitecture="X86" name="HostFission.LookingGlass.Host" type="win32"/>
|
||||
<description>Looking Glass (host)</description>
|
||||
<assemblyIdentity version="1.0.0.0" processorArchitecture="X86" name="hello" type="win32"/>
|
||||
<description>Hello World</description>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
@@ -17,14 +17,14 @@ if(NOT EXISTS "${nvfbc_sdk}/inc" OR NOT IS_DIRECTORY "${nvfbc_sdk}/inc")
|
||||
set(USE_NVFBC OFF)
|
||||
endif()
|
||||
|
||||
if(USE_DXGI)
|
||||
add_capture("DXGI")
|
||||
endif()
|
||||
|
||||
if(USE_NVFBC)
|
||||
add_capture("NVFBC")
|
||||
endif()
|
||||
|
||||
if(USE_DXGI)
|
||||
add_capture("DXGI")
|
||||
endif()
|
||||
|
||||
include("PostCapture")
|
||||
|
||||
add_library(capture STATIC ${CAPTURE_C})
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,22 +1,21 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
/*
|
||||
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 <dxgi.h>
|
||||
#include <d3d11.h>
|
||||
326
c-host/platform/Windows/capture/NVFBC/src/nvfbc.c
Normal file
326
c-host/platform/Windows/capture/NVFBC/src/nvfbc.c
Normal file
@@ -0,0 +1,326 @@
|
||||
/*
|
||||
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/capture.h"
|
||||
#include "interface/platform.h"
|
||||
#include "windows/platform.h"
|
||||
#include "windows/debug.h"
|
||||
#include "windows/mousehook.h"
|
||||
#include "common/option.h"
|
||||
#include "common/framebuffer.h"
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include <NvFBC/nvFBC.h>
|
||||
#include "wrapper.h"
|
||||
|
||||
struct iface
|
||||
{
|
||||
bool stop;
|
||||
NvFBCHandle nvfbc;
|
||||
|
||||
bool seperateCursor;
|
||||
void * pointerShape;
|
||||
unsigned int pointerSize;
|
||||
unsigned int maxWidth, maxHeight;
|
||||
unsigned int width , height;
|
||||
|
||||
uint8_t * frameBuffer;
|
||||
|
||||
NvFBCFrameGrabInfo grabInfo;
|
||||
|
||||
osEventHandle * frameEvent;
|
||||
osEventHandle * cursorEvents[2];
|
||||
|
||||
int mouseX, mouseY, mouseHotX, mouseHotY;
|
||||
bool mouseVisible;
|
||||
};
|
||||
|
||||
static struct iface * this = NULL;
|
||||
|
||||
static void nvfbc_free();
|
||||
|
||||
static void getDesktopSize(unsigned int * width, unsigned int * height)
|
||||
{
|
||||
HMONITOR monitor = MonitorFromWindow(GetDesktopWindow(), MONITOR_DEFAULTTOPRIMARY);
|
||||
MONITORINFO monitorInfo = {
|
||||
.cbSize = sizeof(MONITORINFO)
|
||||
};
|
||||
|
||||
GetMonitorInfo(monitor, &monitorInfo);
|
||||
CloseHandle(monitor);
|
||||
|
||||
*width = monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left;
|
||||
*height = monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top;
|
||||
}
|
||||
|
||||
static void on_mouseMove(int x, int y)
|
||||
{
|
||||
this->mouseX = x;
|
||||
this->mouseY = y;
|
||||
os_signalEvent(this->cursorEvents[0]);
|
||||
}
|
||||
|
||||
static const char * nvfbc_getName()
|
||||
{
|
||||
return "NVFBC (NVidia Frame Buffer Capture)";
|
||||
};
|
||||
|
||||
static void nvfbc_initOptions()
|
||||
{
|
||||
struct Option options[] =
|
||||
{
|
||||
{
|
||||
.module = "nvfbc",
|
||||
.name = "decoupleCursor",
|
||||
.description = "Capture the cursor seperately",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true
|
||||
},
|
||||
{0}
|
||||
};
|
||||
|
||||
option_register(options);
|
||||
}
|
||||
|
||||
static bool nvfbc_create()
|
||||
{
|
||||
if (!NvFBCInit())
|
||||
return false;
|
||||
|
||||
int bufferLen = GetEnvironmentVariable("NVFBC_PRIV_DATA", NULL, 0);
|
||||
uint8_t * privData = NULL;
|
||||
int privDataLen = 0;
|
||||
|
||||
if(bufferLen)
|
||||
{
|
||||
char * buffer = malloc(bufferLen);
|
||||
GetEnvironmentVariable("NVFBC_PRIV_DATA", buffer, bufferLen);
|
||||
|
||||
privDataLen = (bufferLen - 1) / 2;
|
||||
privData = (uint8_t *)malloc(privDataLen);
|
||||
char hex[3] = {0};
|
||||
for(int i = 0; i < privDataLen; ++i)
|
||||
{
|
||||
memcpy(hex, &buffer[i*2], 2);
|
||||
privData[i] = (uint8_t)strtoul(hex, NULL, 16);
|
||||
}
|
||||
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
this = (struct iface *)calloc(sizeof(struct iface), 1);
|
||||
if (!NvFBCToSysCreate(privData, privDataLen, &this->nvfbc, &this->maxWidth, &this->maxHeight))
|
||||
{
|
||||
free(privData);
|
||||
nvfbc_free();
|
||||
return false;
|
||||
}
|
||||
free(privData);
|
||||
|
||||
this->frameEvent = os_createEvent(true);
|
||||
if (!this->frameEvent)
|
||||
{
|
||||
DEBUG_ERROR("failed to create the frame event");
|
||||
nvfbc_free();
|
||||
return false;
|
||||
}
|
||||
|
||||
this->seperateCursor = option_get_bool("nvfbc", "decoupleCursor");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool nvfbc_init(void * pointerShape, const unsigned int pointerSize)
|
||||
{
|
||||
this->stop = false;
|
||||
this->pointerShape = pointerShape;
|
||||
this->pointerSize = pointerSize;
|
||||
|
||||
getDesktopSize(&this->width, &this->height);
|
||||
os_resetEvent(this->frameEvent);
|
||||
|
||||
|
||||
HANDLE event;
|
||||
if (!NvFBCToSysSetup(
|
||||
this->nvfbc,
|
||||
BUFFER_FMT_ARGB,
|
||||
!this->seperateCursor,
|
||||
this->seperateCursor,
|
||||
false,
|
||||
0,
|
||||
(void **)&this->frameBuffer,
|
||||
NULL,
|
||||
&event
|
||||
))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
this->cursorEvents[0] = os_createEvent(true);
|
||||
mouseHook_install(on_mouseMove);
|
||||
|
||||
if (this->seperateCursor)
|
||||
this->cursorEvents[1] = os_wrapEvent(event);
|
||||
|
||||
DEBUG_INFO("Cursor mode : %s", this->seperateCursor ? "decoupled" : "integrated");
|
||||
|
||||
Sleep(100);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void nvfbc_stop()
|
||||
{
|
||||
this->stop = true;
|
||||
os_signalEvent(this->cursorEvents[0]);
|
||||
os_signalEvent(this->frameEvent);
|
||||
}
|
||||
|
||||
static bool nvfbc_deinit()
|
||||
{
|
||||
mouseHook_remove();
|
||||
return true;
|
||||
}
|
||||
|
||||
static void nvfbc_free()
|
||||
{
|
||||
NvFBCToSysRelease(&this->nvfbc);
|
||||
|
||||
if (this->frameEvent)
|
||||
os_freeEvent(this->frameEvent);
|
||||
|
||||
free(this);
|
||||
this = NULL;
|
||||
NvFBCFree();
|
||||
}
|
||||
|
||||
static unsigned int nvfbc_getMaxFrameSize()
|
||||
{
|
||||
return this->maxWidth * this->maxHeight * 4;
|
||||
}
|
||||
|
||||
static CaptureResult nvfbc_capture()
|
||||
{
|
||||
getDesktopSize(&this->width, &this->height);
|
||||
NvFBCFrameGrabInfo grabInfo;
|
||||
CaptureResult result = NvFBCToSysCapture(
|
||||
this->nvfbc,
|
||||
1000,
|
||||
0, 0,
|
||||
this->width,
|
||||
this->height,
|
||||
&grabInfo
|
||||
);
|
||||
|
||||
if (result != CAPTURE_RESULT_OK)
|
||||
return result;
|
||||
|
||||
memcpy(&this->grabInfo, &grabInfo, sizeof(grabInfo));
|
||||
os_signalEvent(this->frameEvent);
|
||||
return CAPTURE_RESULT_OK;
|
||||
}
|
||||
|
||||
static CaptureResult nvfbc_waitFrame(CaptureFrame * frame)
|
||||
{
|
||||
if (!os_waitEvent(this->frameEvent, 1000))
|
||||
return CAPTURE_RESULT_TIMEOUT;
|
||||
|
||||
if (this->stop)
|
||||
return CAPTURE_RESULT_REINIT;
|
||||
|
||||
frame->width = this->grabInfo.dwWidth;
|
||||
frame->height = this->grabInfo.dwHeight;
|
||||
frame->pitch = this->grabInfo.dwBufferWidth * 4;
|
||||
frame->stride = this->grabInfo.dwBufferWidth;
|
||||
|
||||
#if 0
|
||||
//NvFBC never sets bIsHDR so instead we check for any data in the alpha channel
|
||||
//If there is data, it's HDR. This is clearly suboptimal
|
||||
if (!this->grabInfo.bIsHDR)
|
||||
for(int y = 0; y < frame->height; ++y)
|
||||
for(int x = 0; x < frame->width; ++x)
|
||||
{
|
||||
int offset = (y * frame->pitch) + (x * 4);
|
||||
if (this->frameBuffer[offset + 3])
|
||||
{
|
||||
this->grabInfo.bIsHDR = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
frame->format = this->grabInfo.bIsHDR ? CAPTURE_FMT_RGBA10 : CAPTURE_FMT_BGRA;
|
||||
return CAPTURE_RESULT_OK;
|
||||
}
|
||||
|
||||
static CaptureResult nvfbc_getFrame(FrameBuffer frame)
|
||||
{
|
||||
framebuffer_write(
|
||||
frame,
|
||||
this->frameBuffer,
|
||||
this->grabInfo.dwHeight * this->grabInfo.dwBufferWidth * 4
|
||||
);
|
||||
return CAPTURE_RESULT_OK;
|
||||
}
|
||||
|
||||
static CaptureResult nvfbc_getPointer(CapturePointer * pointer)
|
||||
{
|
||||
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])
|
||||
{
|
||||
result = NvFBCToSysGetCursor(this->nvfbc, pointer, this->pointerShape, this->pointerSize);
|
||||
this->mouseVisible = pointer->visible;
|
||||
this->mouseHotX = pointer->x;
|
||||
this->mouseHotY = pointer->y;
|
||||
if (result != CAPTURE_RESULT_OK)
|
||||
return result;
|
||||
}
|
||||
|
||||
pointer->visible = this->mouseVisible;
|
||||
pointer->x = this->mouseX - this->mouseHotX;
|
||||
pointer->y = this->mouseY - this->mouseHotY;
|
||||
return CAPTURE_RESULT_OK;
|
||||
}
|
||||
|
||||
struct CaptureInterface Capture_NVFBC =
|
||||
{
|
||||
.getName = nvfbc_getName,
|
||||
.initOptions = nvfbc_initOptions,
|
||||
|
||||
.create = nvfbc_create,
|
||||
.init = nvfbc_init,
|
||||
.stop = nvfbc_stop,
|
||||
.deinit = nvfbc_deinit,
|
||||
.free = nvfbc_free,
|
||||
.getMaxFrameSize = nvfbc_getMaxFrameSize,
|
||||
.capture = nvfbc_capture,
|
||||
.waitFrame = nvfbc_waitFrame,
|
||||
.getFrame = nvfbc_getFrame,
|
||||
.getPointer = nvfbc_getPointer
|
||||
};
|
||||
@@ -1,25 +1,24 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
/*
|
||||
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 "wrapper.h"
|
||||
#include "common/windebug.h"
|
||||
#include "windows/debug.h"
|
||||
#include <windows.h>
|
||||
#include <NvFBC/nvFBCToSys.h>
|
||||
|
||||
@@ -253,11 +252,6 @@ CaptureResult NvFBCToSysCapture(
|
||||
handle->retry = 0;
|
||||
break;
|
||||
|
||||
case NVFBC_ERROR_PROTECTED_CONTENT:
|
||||
DEBUG_WARN("Protected content is playing, can't capture");
|
||||
Sleep(100);
|
||||
return CAPTURE_RESULT_TIMEOUT;
|
||||
|
||||
case NVFBC_ERROR_INVALID_PARAM:
|
||||
if (handle->retry < 2)
|
||||
{
|
||||
@@ -294,8 +288,8 @@ CaptureResult NvFBCToSysGetCursor(NvFBCHandle handle, CapturePointer * pointer,
|
||||
return CAPTURE_RESULT_ERROR;
|
||||
}
|
||||
|
||||
pointer->hx = params.dwXHotSpot;
|
||||
pointer->hy = params.dwYHotSpot;
|
||||
pointer->x = params.dwXHotSpot;
|
||||
pointer->y = params.dwYHotSpot;
|
||||
pointer->width = params.dwWidth;
|
||||
pointer->height = params.dwHeight;
|
||||
pointer->pitch = params.dwPitch;
|
||||
@@ -333,4 +327,4 @@ CaptureResult NvFBCToSysGetCursor(NvFBCHandle handle, CapturePointer * pointer,
|
||||
|
||||
memcpy(buffer, params.pBits, params.dwBufferSize);
|
||||
return CAPTURE_RESULT_OK;
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,21 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
/*
|
||||
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 <stdbool.h>
|
||||
#include <NvFBC/nvFBC.h>
|
||||
35
c-host/platform/Windows/include/windows/debug.h
Normal file
35
c-host/platform/Windows/include/windows/debug.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
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 "common/debug.h"
|
||||
#include <windows.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void DebugWinError(const char * file, const unsigned int line, const char * function, const char * desc, HRESULT status);
|
||||
|
||||
#define DEBUG_WINERROR(x, y) DebugWinError(STRIPPATH(__FILE__), __LINE__, __FUNCTION__, x, y)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
23
c-host/platform/Windows/include/windows/mousehook.h
Normal file
23
c-host/platform/Windows/include/windows/mousehook.h
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
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
|
||||
*/
|
||||
|
||||
typedef void (*MouseHookFn)(int x, int y);
|
||||
|
||||
void mouseHook_install(MouseHookFn callback);
|
||||
void mouseHook_remove();
|
||||
23
c-host/platform/Windows/include/windows/platform.h
Normal file
23
c-host/platform/Windows/include/windows/platform.h
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
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 <windows.h>
|
||||
|
||||
osEventHandle * os_wrapEvent(HANDLE event);
|
||||
98
c-host/platform/Windows/src/mousehook.c
Normal file
98
c-host/platform/Windows/src/mousehook.c
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
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 "windows/mousehook.h"
|
||||
#include "windows/debug.h"
|
||||
#include "platform.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
struct mouseHook
|
||||
{
|
||||
bool installed;
|
||||
HHOOK hook;
|
||||
MouseHookFn callback;
|
||||
};
|
||||
|
||||
static struct mouseHook mouseHook = { 0 };
|
||||
|
||||
// forwards
|
||||
static LRESULT WINAPI mouseHook_hook(int nCode, WPARAM wParam, LPARAM lParam);
|
||||
static LRESULT msg_callback(WPARAM wParam, LPARAM lParam);
|
||||
|
||||
void mouseHook_install(MouseHookFn callback)
|
||||
{
|
||||
struct MSG_CALL_FUNCTION cf;
|
||||
cf.fn = msg_callback;
|
||||
cf.wParam = 1;
|
||||
cf.lParam = (LPARAM)callback;
|
||||
sendAppMessage(WM_CALL_FUNCTION, 0, (LPARAM)&cf);
|
||||
}
|
||||
|
||||
void mouseHook_remove()
|
||||
{
|
||||
struct MSG_CALL_FUNCTION cf;
|
||||
cf.fn = msg_callback;
|
||||
cf.wParam = 0;
|
||||
cf.lParam = 0;
|
||||
sendAppMessage(WM_CALL_FUNCTION, 0, (LPARAM)&cf);
|
||||
}
|
||||
|
||||
static LRESULT msg_callback(WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
if (wParam)
|
||||
{
|
||||
if (mouseHook.installed)
|
||||
{
|
||||
DEBUG_WARN("Mouse hook already installed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
mouseHook.hook = SetWindowsHookEx(WH_MOUSE_LL, mouseHook_hook, NULL, 0);
|
||||
if (!mouseHook.hook)
|
||||
{
|
||||
DEBUG_WINERROR("Failed to install the mouse hook", GetLastError());
|
||||
return 0;
|
||||
}
|
||||
|
||||
mouseHook.installed = true;
|
||||
mouseHook.callback = (MouseHookFn)lParam;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!mouseHook.installed)
|
||||
return 0;
|
||||
|
||||
UnhookWindowsHookEx(mouseHook.hook);
|
||||
mouseHook.installed = false;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static LRESULT WINAPI mouseHook_hook(int nCode, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
if (nCode == HC_ACTION && wParam == WM_MOUSEMOVE)
|
||||
{
|
||||
MSLLHOOKSTRUCT *msg = (MSLLHOOKSTRUCT *)lParam;
|
||||
mouseHook.callback(msg->pt.x, msg->pt.y);
|
||||
}
|
||||
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);
|
||||
}
|
||||
33
c-host/platform/Windows/src/platform.h
Normal file
33
c-host/platform/Windows/src/platform.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
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 <windows.h>
|
||||
|
||||
#define WM_CALL_FUNCTION (WM_USER+1)
|
||||
#define WM_TRAYICON (WM_USER+2)
|
||||
|
||||
typedef LRESULT (*CallFunction)(WPARAM wParam, LPARAM lParam);
|
||||
struct MSG_CALL_FUNCTION
|
||||
{
|
||||
CallFunction fn;
|
||||
WPARAM wParam;
|
||||
LPARAM lParam;
|
||||
};
|
||||
|
||||
LRESULT sendAppMessage(UINT Msg, WPARAM wParam, LPARAM lParam);
|
||||
42
c-host/platform/Windows/src/windebug.c
Normal file
42
c-host/platform/Windows/src/windebug.c
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
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 "windows/debug.h"
|
||||
#include <stdio.h>
|
||||
|
||||
void DebugWinError(const char * file, const unsigned int line, const char * function, const char * desc, HRESULT status)
|
||||
{
|
||||
char *buffer;
|
||||
FormatMessageA(
|
||||
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER,
|
||||
NULL,
|
||||
status,
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
(char*)&buffer,
|
||||
1024,
|
||||
NULL
|
||||
);
|
||||
|
||||
for(size_t i = strlen(buffer) - 1; i > 0; --i)
|
||||
if (buffer[i] == '\n' || buffer[i] == '\r')
|
||||
buffer[i] = 0;
|
||||
|
||||
fprintf(stderr, "[E] %20s:%-4u | %-30s | %s: 0x%08x (%s)\n", file, line, function, desc, (int)status, buffer);
|
||||
LocalFree(buffer);
|
||||
}
|
||||
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()
|
||||
@@ -1,7 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(looking-glass-client C)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/")
|
||||
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/")
|
||||
|
||||
include(GNUInstallDirs)
|
||||
include(CheckCCompilerFlag)
|
||||
@@ -13,8 +13,6 @@ if(OPTIMIZE_FOR_NATIVE)
|
||||
if(COMPILER_SUPPORTS_MARCH_NATIVE)
|
||||
add_compile_options("-march=native")
|
||||
endif()
|
||||
else()
|
||||
add_compile_options("-march=nehalem" "-mtune=generic")
|
||||
endif()
|
||||
|
||||
option(ENABLE_OPENGL "Enable the OpenGL renderer" ON)
|
||||
@@ -23,37 +21,14 @@ add_feature_info(ENABLE_OPENGL ENABLE_OPENGL "Legacy OpenGL renderer.")
|
||||
option(ENABLE_EGL "Enable the EGL renderer" ON)
|
||||
add_feature_info(ENABLE_EGL ENABLE_EGL "EGL renderer.")
|
||||
|
||||
option(ENABLE_CB_X11 "Enable X11 clipboard integration" ON)
|
||||
add_feature_info(ENABLE_CB_X11 ENABLE_CB_X11 "X11 Clipboard Integration.")
|
||||
|
||||
option(ENABLE_BACKTRACE "Enable backtrace support on crash" ON)
|
||||
add_feature_info(ENABLE_BACKTRACE ENABLE_BACKTRACE "Backtrace support.")
|
||||
|
||||
option(ENABLE_ASAN "Build with AddressSanitizer" OFF)
|
||||
add_feature_info(ENABLE_ASAN ENABLE_ASAN "AddressSanitizer support.")
|
||||
|
||||
option(ENABLE_UBSAN "Build with UndefinedBehaviorSanitizer" OFF)
|
||||
add_feature_info(ENABLE_UBSAN ENABLE_UBSAN "UndefinedBehaviorSanitizer support.")
|
||||
|
||||
option(ENABLE_SDL "Build with SDL support" OFF)
|
||||
add_feature_info(ENABLE_SDL ENABLE_SDL "SDL support.")
|
||||
|
||||
option(ENABLE_X11 "Build with X11 support" ON)
|
||||
add_feature_info(ENABLE_X11 ENABLE_X11 "X11 support.")
|
||||
|
||||
option(ENABLE_WAYLAND "Build with Wayland support" ON)
|
||||
add_feature_info(ENABLE_WAYLAND ENABLE_WAYLAND "Wayland support.")
|
||||
|
||||
option(ENABLE_LIBDECOR "Build with libdecor support" OFF)
|
||||
add_feature_info(ENABLE_LIBDECOR ENABLE_LIBDECOR "libdecor support.")
|
||||
|
||||
if (NOT ENABLE_SDL AND NOT ENABLE_X11 AND NOT ENABLE_WAYLAND)
|
||||
message(FATAL_ERROR "One of ENABLE_SDL, ENABLE_X11, or ENABLE_WAYLAND must be on")
|
||||
endif()
|
||||
|
||||
add_compile_options(
|
||||
"-Wall"
|
||||
"-Wextra"
|
||||
"-Wno-sign-compare"
|
||||
"-Wno-unused-parameter"
|
||||
"$<$<C_COMPILER_ID:GNU>:-Wimplicit-fallthrough=2>"
|
||||
"-Werror"
|
||||
"-Wfatal-errors"
|
||||
"-ffast-math"
|
||||
@@ -62,89 +37,70 @@ add_compile_options(
|
||||
"$<$<CONFIG:DEBUG>:-O0;-g3;-ggdb>"
|
||||
)
|
||||
|
||||
set(EXE_FLAGS "-Wl,--gc-sections -z noexecstack")
|
||||
set(EXE_FLAGS "-Wl,--gc-sections")
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
|
||||
if (ENABLE_OPENGL)
|
||||
add_definitions(-D ENABLE_OPENGL)
|
||||
endif()
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(PKGCONFIG REQUIRED
|
||||
sdl2
|
||||
x11
|
||||
)
|
||||
|
||||
if (ENABLE_EGL)
|
||||
add_definitions(-D ENABLE_EGL)
|
||||
endif()
|
||||
|
||||
if(ENABLE_ASAN)
|
||||
add_compile_options("-fno-omit-frame-pointer" "-fsanitize=address")
|
||||
set(EXE_FLAGS "${EXE_FLAGS} -fno-omit-frame-pointer -fsanitize=address")
|
||||
endif()
|
||||
|
||||
if(ENABLE_UBSAN)
|
||||
add_compile_options("-fsanitize=undefined")
|
||||
set(EXE_FLAGS "${EXE_FLAGS} -fsanitize=undefined")
|
||||
endif()
|
||||
execute_process(
|
||||
COMMAND cat ../VERSION
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE BUILD_VERSION
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
|
||||
find_package(GMP)
|
||||
|
||||
add_definitions(-D BUILD_VERSION='"${BUILD_VERSION}"')
|
||||
add_definitions(-D ATOMIC_LOCKING)
|
||||
add_definitions(-D GL_GLEXT_PROTOTYPES)
|
||||
get_filename_component(PROJECT_TOP "${PROJECT_SOURCE_DIR}/.." ABSOLUTE)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_BINARY_DIR}/version.c
|
||||
${CMAKE_BINARY_DIR}/_version.c
|
||||
COMMAND ${CMAKE_COMMAND} -D PROJECT_TOP=${PROJECT_TOP} -P
|
||||
${PROJECT_TOP}/version.cmake
|
||||
)
|
||||
|
||||
include_directories(
|
||||
${PROJECT_SOURCE_DIR}/include
|
||||
${CMAKE_BINARY_DIR}/include
|
||||
${PKGCONFIG_INCLUDE_DIRS}
|
||||
${GMP_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
link_libraries(
|
||||
${PKGCONFIG_LIBRARIES}
|
||||
${GMP_LIBRARIES}
|
||||
${CMAKE_DL_LIBS}
|
||||
rt
|
||||
m
|
||||
)
|
||||
|
||||
set(SOURCES
|
||||
${CMAKE_BINARY_DIR}/version.c
|
||||
src/main.c
|
||||
src/core.c
|
||||
src/app.c
|
||||
src/config.c
|
||||
src/keybind.c
|
||||
src/lg-renderer.c
|
||||
src/ll.c
|
||||
src/util.c
|
||||
src/clipboard.c
|
||||
src/kb.c
|
||||
src/egl_dynprocs.c
|
||||
src/utils.c
|
||||
)
|
||||
|
||||
add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common" )
|
||||
add_subdirectory("${PROJECT_TOP}/repos/LGMP/lgmp" "${CMAKE_BINARY_DIR}/LGMP" )
|
||||
add_subdirectory("${PROJECT_TOP}/repos/PureSpice" "${CMAKE_BINARY_DIR}/PureSpice")
|
||||
|
||||
add_subdirectory(displayservers)
|
||||
add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common")
|
||||
add_subdirectory(spice)
|
||||
add_subdirectory(renderers)
|
||||
add_subdirectory(clipboards)
|
||||
add_subdirectory(fonts)
|
||||
add_subdirectory(decoders)
|
||||
|
||||
add_executable(looking-glass-client ${SOURCES})
|
||||
target_compile_options(looking-glass-client PUBLIC ${PKGCONFIG_CFLAGS_OTHER})
|
||||
target_link_libraries(looking-glass-client
|
||||
${EXE_FLAGS}
|
||||
lg_common
|
||||
displayservers
|
||||
lgmp
|
||||
purespice
|
||||
spice
|
||||
renderers
|
||||
clipboards
|
||||
fonts
|
||||
)
|
||||
|
||||
install(TARGETS looking-glass-client
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
COMPONENT binary)
|
||||
install(PROGRAMS ${CMAKE_BINARY_DIR}/looking-glass-client DESTINATION bin/ COMPONENT binary)
|
||||
|
||||
feature_summary(WHAT ENABLED_FEATURES DISABLED_FEATURES)
|
||||
|
||||
162
client/README.md
Normal file
162
client/README.md
Normal file
@@ -0,0 +1,162 @@
|
||||
# Looking Glass Client
|
||||
|
||||
This is the Looking Glass client application that is designed to work in tandem with the Looking Glass Host application
|
||||
|
||||
---
|
||||
|
||||
## Building the Application
|
||||
|
||||
### Build Dependencies
|
||||
|
||||
* binutils-dev
|
||||
* cmake
|
||||
* fonts-freefont-ttf
|
||||
* libsdl2-dev
|
||||
* libsdl2-ttf-dev
|
||||
* libspice-protocol-dev
|
||||
* libfontconfig1-dev
|
||||
* libx11-dev
|
||||
* nettle-dev
|
||||
|
||||
#### Debian (and maybe Ubuntu)
|
||||
|
||||
apt-get install binutils-dev cmake fonts-freefont-ttf libsdl2-dev libsdl2-ttf-dev libspice-protocol-dev libfontconfig1-dev libx11-dev nettle-dev
|
||||
|
||||
### Building
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ../
|
||||
make
|
||||
|
||||
Should this all go well you should be left with the file `looking-glass-client`
|
||||
|
||||
---
|
||||
|
||||
## Usage Tips
|
||||
|
||||
### Key Bindings
|
||||
|
||||
By default Looking Glass uses the `Scroll Lock` key as the escape key for commands as well as the input capture mode toggle, this can be changed using the `-m` switch if you desire a different key.
|
||||
Below are a list of current key bindings:
|
||||
|
||||
| Command | Description |
|
||||
|-|-|
|
||||
| <kbd>ScrLk</kbd> | Toggle cursor screen capture |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F</kbd> | Full Screen 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>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>F1</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F1</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F2</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F2</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F3</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F3</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F4</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F4</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F5</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F5</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F6</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F6</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F7</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F7</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F8</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F8</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F9</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F9</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F10</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F10</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F11</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F11</kbd> to the guest |
|
||||
| <kbd>ScrLk</kbd>+<kbd>F12</kbd> | Send <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>F12</kbd> to the guest |
|
||||
|
||||
|
||||
|
||||
### Setting options via command line arguments
|
||||
|
||||
The syntax is simple: `module:name=value`, for example:
|
||||
|
||||
./looking-glass-client win:fullScreen=yes egl:nvGain=1
|
||||
|
||||
### Setting options via configuration files
|
||||
|
||||
By default the application will look for and load the config files in the following locations
|
||||
|
||||
* /etc/looking-glass-client.ini
|
||||
* ~/.looking-glass-client.ini
|
||||
|
||||
The format of this file is the commonly known INI format, for example:
|
||||
|
||||
[win]
|
||||
fullScreen=yes
|
||||
|
||||
[egl]
|
||||
nvGain=1
|
||||
|
||||
Command line arguments will override any options loaded from the config files.
|
||||
|
||||
### Supported options
|
||||
|
||||
```
|
||||
|-------------------------------------------------------------------------------------------------------------------------|
|
||||
| Long | Short | Value | Description |
|
||||
|-------------------------------------------------------------------------------------------------------------------------|
|
||||
| app:configFile | -C | NULL | A file to read additional configuration from |
|
||||
| app:shmFile | -f | /dev/shm/looking-glass | The path to the shared memory file |
|
||||
| app:shmSize | -L | 0 | Specify the size in MB of the shared memory file (0 = detect) |
|
||||
| app:renderer | -g | auto | Specify the renderer to use |
|
||||
| app:license | -l | no | Show the license for this application and then terminate |
|
||||
| app:cursorPollInterval | | 1000 | How often to check for a cursor update in microseconds |
|
||||
| app:framePollInterval | | 1000 | How often to check for a frame update in microseconds |
|
||||
|-------------------------------------------------------------------------------------------------------------------------|
|
||||
|
||||
|-------------------------------------------------------------------------------------------------------------|
|
||||
| Long | Short | Value | Description |
|
||||
|-------------------------------------------------------------------------------------------------------------|
|
||||
| win:title | | Looking Glass (client) | The window title |
|
||||
| win:position | | center | Initial window position at startup |
|
||||
| win:size | | 1024x768 | Initial window size at startup |
|
||||
| win:autoResize | -a | no | Auto resize the window to the guest |
|
||||
| win:allowResize | -n | yes | Aallow the window to be manually resized |
|
||||
| win:keepAspect | -r | yes | Maintain the correct aspect ratio |
|
||||
| win:borderless | -d | no | Borderless mode |
|
||||
| win:fullScreen | -F | no | Launch in fullscreen borderless mode |
|
||||
| win:maximize | -T | no | Launch window maximized |
|
||||
| win:minimizeOnFocusLoss | | yes | Minimize window on focus loss |
|
||||
| win:fpsLimit | -K | 200 | Frame rate limit (0 = disable - not recommended) |
|
||||
| win:showFPS | -k | no | Enable the FPS & UPS display |
|
||||
| win:ignoreQuit | -Q | no | Ignore requests to quit (ie: Alt+F4) |
|
||||
| win:noScreensaver | -S | no | Prevent the screensaver from starting |
|
||||
| win:alerts | -q | yes | Show on screen alert messages |
|
||||
|-------------------------------------------------------------------------------------------------------------|
|
||||
|
||||
|---------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Long | Short | Value | Description |
|
||||
|---------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| input:grabKeyboard | -G | yes | Grab the keyboard in capture mode |
|
||||
| input:escapeKey | -m | 71 = ScrollLock | Specify the escape key, see https://wiki.libsdl.org/SDLScancodeLookup for valid values |
|
||||
| input:hideCursor | -M | yes | Hide the local mouse cursor |
|
||||
| input:mouseSens | | 0 | Initial mouse sensitivity when in capture mode (-9 to 9) |
|
||||
|---------------------------------------------------------------------------------------------------------------------------------------|
|
||||
|
||||
|------------------------------------------------------------------------------------------------------------------|
|
||||
| Long | Short | Value | Description |
|
||||
|------------------------------------------------------------------------------------------------------------------|
|
||||
| spice:enable | -s | yes | Enable the built in SPICE client for input and/or clipboard support |
|
||||
| spice:host | -c | 127.0.0.1 | The SPICE server host or UNIX socket |
|
||||
| spice:port | -p | 5900 | The SPICE server port (0 = unix socket) |
|
||||
| spice:input | | yes | Use SPICE to send keyboard and mouse input events to the guest |
|
||||
| spice:clipboard | | yes | Use SPICE to syncronize the clipboard contents with the guest |
|
||||
| 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:scaleCursor | -j | yes | Scale cursor input position to screen size when up/down scaled |
|
||||
|------------------------------------------------------------------------------------------------------------------|
|
||||
|
||||
|--------------------------------------------------------------------------|
|
||||
| Long | Short | Value | Description |
|
||||
|--------------------------------------------------------------------------|
|
||||
| egl:vsync | | no | Enable vsync |
|
||||
| egl:nvGainMax | | 1 | The maximum night vision gain |
|
||||
| egl:nvGain | | 0 | The initial night vision gain at startup |
|
||||
|--------------------------------------------------------------------------|
|
||||
|
||||
|------------------------------------------------------------------------------------|
|
||||
| Long | Short | Value | Description |
|
||||
|------------------------------------------------------------------------------------|
|
||||
| opengl:mipmap | | yes | Enable mipmapping |
|
||||
| opengl:vsync | | yes | Enable vsync |
|
||||
| opengl:preventBuffer | | yes | Prevent the driver from buffering frames |
|
||||
| opengl:amdPinnedMem | | yes | Use GL_AMD_pinned_memory if it is available |
|
||||
|------------------------------------------------------------------------------------|
|
||||
```
|
||||
43
client/clipboards/CMakeLists.txt
Normal file
43
client/clipboards/CMakeLists.txt
Normal file
@@ -0,0 +1,43 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(clipboards LANGUAGES C)
|
||||
|
||||
set(CLIPBOARD_H "${CMAKE_BINARY_DIR}/include/dynamic/clipboards.h")
|
||||
set(CLIPBOARD_C "${CMAKE_BINARY_DIR}/src/clipboards.c")
|
||||
|
||||
file(WRITE ${CLIPBOARD_H} "#include \"interface/clipboard.h\"\n\n")
|
||||
file(APPEND ${CLIPBOARD_H} "extern LG_Clipboard * LG_Clipboards[];\n\n")
|
||||
|
||||
file(WRITE ${CLIPBOARD_C} "#include \"interface/clipboard.h\"\n\n")
|
||||
file(APPEND ${CLIPBOARD_C} "#include <stddef.h>\n\n")
|
||||
|
||||
set(CLIPBOARDS "_")
|
||||
set(CLIPBOARDS_LINK "_")
|
||||
function(add_clipboard name)
|
||||
set(CLIPBOARDS "${CLIPBOARDS};${name}" PARENT_SCOPE)
|
||||
set(CLIPBOARDS_LINK "${CLIPBOARDS_LINK};clipboard_${name}" PARENT_SCOPE)
|
||||
add_subdirectory(${name})
|
||||
endfunction()
|
||||
|
||||
# Add/remove clipboards here!
|
||||
if (ENABLE_CB_X11)
|
||||
add_clipboard(X11)
|
||||
endif()
|
||||
|
||||
list(REMOVE_AT CLIPBOARDS 0)
|
||||
list(REMOVE_AT CLIPBOARDS_LINK 0)
|
||||
|
||||
list(LENGTH CLIPBOARDS CLIPBOARD_COUNT)
|
||||
file(APPEND ${CLIPBOARD_H} "#define LG_CLIPBOARD_COUNT ${CLIPBOARD_COUNT}\n")
|
||||
|
||||
foreach(clipboard ${CLIPBOARDS})
|
||||
file(APPEND ${CLIPBOARD_C} "extern LG_Clipboard LGC_${clipboard};\n")
|
||||
endforeach()
|
||||
|
||||
file(APPEND ${CLIPBOARD_C} "\nconst LG_Clipboard * LG_Clipboards[] =\n{\n")
|
||||
foreach(clipboard ${CLIPBOARDS})
|
||||
file(APPEND ${CLIPBOARD_C} " &LGC_${clipboard},\n")
|
||||
endforeach()
|
||||
file(APPEND ${CLIPBOARD_C} " NULL\n};\n\n")
|
||||
|
||||
add_library(clipboards STATIC ${CLIPBOARD_C})
|
||||
target_link_libraries(clipboards ${CLIPBOARDS_LINK})
|
||||
26
client/clipboards/X11/CMakeLists.txt
Normal file
26
client/clipboards/X11/CMakeLists.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(clipboard_X11 LANGUAGES C)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(CLIPBOARD_PKGCONFIG REQUIRED
|
||||
x11
|
||||
xfixes
|
||||
)
|
||||
|
||||
add_library(clipboard_X11 STATIC
|
||||
src/x11.c
|
||||
)
|
||||
|
||||
target_link_libraries(clipboard_X11
|
||||
${CLIPBOARD_PKGCONFIG_LIBRARIES}
|
||||
lg_common
|
||||
)
|
||||
|
||||
target_include_directories(clipboard_X11
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
PRIVATE
|
||||
src
|
||||
${CLIPBOARD_PKGCONFIG_INCLUDE_DIRS}
|
||||
)
|
||||
359
client/clipboards/X11/src/x11.c
Normal file
359
client/clipboards/X11/src/x11.c
Normal file
@@ -0,0 +1,359 @@
|
||||
/*
|
||||
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/clipboard.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
#include <X11/extensions/Xfixes.h>
|
||||
|
||||
struct state
|
||||
{
|
||||
Display * display;
|
||||
Window window;
|
||||
Atom aSelection;
|
||||
Atom aCurSelection;
|
||||
Atom aTargets;
|
||||
Atom aSelData;
|
||||
Atom aIncr;
|
||||
Atom aTypes[LG_CLIPBOARD_DATA_NONE];
|
||||
LG_ClipboardReleaseFn releaseFn;
|
||||
LG_ClipboardRequestFn requestFn;
|
||||
LG_ClipboardNotifyFn notifyFn;
|
||||
LG_ClipboardDataFn dataFn;
|
||||
LG_ClipboardData type;
|
||||
|
||||
// XFixes vars
|
||||
int eventBase;
|
||||
int errorBase;
|
||||
};
|
||||
|
||||
static struct state * this = NULL;
|
||||
|
||||
static const char * atomTypes[] =
|
||||
{
|
||||
"UTF8_STRING",
|
||||
"image/png",
|
||||
"image/bmp",
|
||||
"image/tiff",
|
||||
"image/jpeg"
|
||||
};
|
||||
|
||||
static const char * x11_cb_getName()
|
||||
{
|
||||
return "X11";
|
||||
}
|
||||
|
||||
static bool x11_cb_init(
|
||||
SDL_SysWMinfo * wminfo,
|
||||
LG_ClipboardReleaseFn releaseFn,
|
||||
LG_ClipboardNotifyFn notifyFn,
|
||||
LG_ClipboardDataFn dataFn)
|
||||
{
|
||||
// final sanity check
|
||||
if (wminfo->subsystem != SDL_SYSWM_X11)
|
||||
{
|
||||
DEBUG_ERROR("wrong subsystem");
|
||||
return false;
|
||||
}
|
||||
|
||||
this = (struct state *)malloc(sizeof(struct state));
|
||||
memset(this, 0, sizeof(struct state));
|
||||
|
||||
this->display = wminfo->info.x11.display;
|
||||
this->window = wminfo->info.x11.window;
|
||||
this->aSelection = XInternAtom(this->display, "CLIPBOARD", False);
|
||||
this->aTargets = XInternAtom(this->display, "TARGETS" , False);
|
||||
this->aSelData = XInternAtom(this->display, "SEL_DATA" , False);
|
||||
this->aIncr = XInternAtom(this->display, "INCR" , False);
|
||||
this->aCurSelection = BadValue;
|
||||
this->releaseFn = releaseFn;
|
||||
this->notifyFn = notifyFn;
|
||||
this->dataFn = dataFn;
|
||||
|
||||
for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
{
|
||||
this->aTypes[i] = XInternAtom(this->display, atomTypes[i], False);
|
||||
if (this->aTypes[i] == BadAlloc || this->aTypes[i] == BadValue)
|
||||
{
|
||||
DEBUG_ERROR("failed to get atom for type: %s", atomTypes[i]);
|
||||
free(this);
|
||||
this = NULL;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// we need the raw X events
|
||||
SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
|
||||
|
||||
// use xfixes to get clipboard change notifications
|
||||
if (!XFixesQueryExtension(this->display, &this->eventBase, &this->errorBase))
|
||||
{
|
||||
DEBUG_ERROR("failed to initialize xfixes");
|
||||
free(this);
|
||||
this = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
XFixesSelectSelectionInput(this->display, this->window, XA_PRIMARY , XFixesSetSelectionOwnerNotifyMask);
|
||||
XFixesSelectSelectionInput(this->display, this->window, this->aSelection, XFixesSetSelectionOwnerNotifyMask);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void x11_cb_free()
|
||||
{
|
||||
free(this);
|
||||
this = NULL;
|
||||
}
|
||||
|
||||
static void x11_cb_reply_fn(void * opaque, LG_ClipboardData type, uint8_t * data, uint32_t size)
|
||||
{
|
||||
XEvent *s = (XEvent *)opaque;
|
||||
|
||||
XChangeProperty(
|
||||
this->display ,
|
||||
s->xselection.requestor,
|
||||
s->xselection.property ,
|
||||
s->xselection.target ,
|
||||
8,
|
||||
PropModeReplace,
|
||||
data,
|
||||
size);
|
||||
|
||||
XSendEvent(this->display, s->xselection.requestor, 0, 0, s);
|
||||
XFlush(this->display);
|
||||
free(s);
|
||||
}
|
||||
|
||||
static void x11_cb_wmevent(SDL_SysWMmsg * msg)
|
||||
{
|
||||
XEvent e = msg->msg.x11.event;
|
||||
|
||||
if (e.type == SelectionRequest)
|
||||
{
|
||||
XEvent * s = (XEvent *)malloc(sizeof(XEvent));
|
||||
s->xselection.type = SelectionNotify;
|
||||
s->xselection.requestor = e.xselectionrequest.requestor;
|
||||
s->xselection.selection = e.xselectionrequest.selection;
|
||||
s->xselection.target = e.xselectionrequest.target;
|
||||
s->xselection.property = e.xselectionrequest.property;
|
||||
s->xselection.time = e.xselectionrequest.time;
|
||||
|
||||
if (!this->requestFn)
|
||||
{
|
||||
s->xselection.property = None;
|
||||
XSendEvent(this->display, e.xselectionrequest.requestor, 0, 0, s);
|
||||
XFlush(this->display);
|
||||
free(s);
|
||||
return;
|
||||
}
|
||||
|
||||
// target list requested
|
||||
if (e.xselectionrequest.target == this->aTargets)
|
||||
{
|
||||
Atom targets[2];
|
||||
targets[0] = this->aTargets;
|
||||
targets[1] = this->aTypes[this->type];
|
||||
|
||||
XChangeProperty(
|
||||
e.xselectionrequest.display,
|
||||
e.xselectionrequest.requestor,
|
||||
e.xselectionrequest.property,
|
||||
XA_ATOM,
|
||||
32,
|
||||
PropModeReplace,
|
||||
(unsigned char*)targets,
|
||||
sizeof(targets) / sizeof(Atom));
|
||||
|
||||
XSendEvent(this->display, e.xselectionrequest.requestor, 0, 0, s);
|
||||
XFlush(this->display);
|
||||
free(s);
|
||||
return;
|
||||
}
|
||||
|
||||
// look to see if we can satisfy the data type
|
||||
for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
if (this->aTypes[i] == e.xselectionrequest.target && this->type == i)
|
||||
{
|
||||
// request the data
|
||||
this->requestFn(x11_cb_reply_fn, s);
|
||||
return;
|
||||
}
|
||||
|
||||
// report no data
|
||||
s->xselection.property = None;
|
||||
XSendEvent(this->display, e.xselectionrequest.requestor, 0, 0, s);
|
||||
XFlush(this->display);
|
||||
}
|
||||
|
||||
if (e.type == SelectionClear && (
|
||||
e.xselectionclear.selection == XA_PRIMARY ||
|
||||
e.xselectionclear.selection == this->aSelection)
|
||||
)
|
||||
{
|
||||
this->aCurSelection = BadValue;
|
||||
this->releaseFn();
|
||||
return;
|
||||
}
|
||||
|
||||
// if someone selected data
|
||||
if (e.type == this->eventBase + XFixesSelectionNotify)
|
||||
{
|
||||
XFixesSelectionNotifyEvent * sne = (XFixesSelectionNotifyEvent *)&e;
|
||||
|
||||
// check if the selection is valid and it isn't ourself
|
||||
if (
|
||||
(sne->selection != XA_PRIMARY && sne->selection != this->aSelection) ||
|
||||
sne->owner == this->window ||
|
||||
sne->owner == 0
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// remember which selection we are working with
|
||||
this->aCurSelection = sne->selection;
|
||||
XConvertSelection(
|
||||
this->display,
|
||||
sne->selection,
|
||||
this->aTargets,
|
||||
this->aTargets,
|
||||
this->window,
|
||||
CurrentTime);
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.type == SelectionNotify)
|
||||
{
|
||||
if (e.xselection.property == None)
|
||||
return;
|
||||
|
||||
Atom type;
|
||||
int format;
|
||||
unsigned long itemCount, after;
|
||||
unsigned char *data;
|
||||
|
||||
XGetWindowProperty(
|
||||
this->display,
|
||||
this->window,
|
||||
e.xselection.property,
|
||||
0, ~0L, // start and length
|
||||
True , // delete the property
|
||||
AnyPropertyType,
|
||||
&type,
|
||||
&format,
|
||||
&itemCount,
|
||||
&after,
|
||||
&data);
|
||||
|
||||
// the target list
|
||||
if (e.xselection.property == this->aTargets)
|
||||
{
|
||||
// the format is 32-bit and we must have data
|
||||
// this is technically incorrect however as it's
|
||||
// an array of padded 64-bit values
|
||||
if (!data || format != 32)
|
||||
{
|
||||
if (data)
|
||||
XFree(data);
|
||||
return;
|
||||
}
|
||||
|
||||
// see if we support any of the targets listed
|
||||
const uint64_t * targets = (const uint64_t *)data;
|
||||
for(unsigned long i = 0; i < itemCount; ++i)
|
||||
{
|
||||
for(int n = 0; n < LG_CLIPBOARD_DATA_NONE; ++n)
|
||||
if (this->aTypes[n] == targets[i])
|
||||
{
|
||||
// we have a match, so send the notification
|
||||
this->notifyFn(n);
|
||||
XFree(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// no matches
|
||||
this->notifyFn(LG_CLIPBOARD_DATA_NONE);
|
||||
XFree(data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (format == this->aIncr)
|
||||
{
|
||||
DEBUG_WARN("fixme: large paste buffers are not yet supported");
|
||||
XFree(data);
|
||||
return;
|
||||
}
|
||||
|
||||
for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
if (this->aTypes[i] == type)
|
||||
{
|
||||
this->dataFn(i, data, itemCount);
|
||||
XFree(data);
|
||||
return;
|
||||
}
|
||||
|
||||
DEBUG_WARN("clipboard data (%s) not in a supported format", XGetAtomName(this->display, type));
|
||||
XFree(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void x11_cb_notice(LG_ClipboardRequestFn requestFn, LG_ClipboardData type)
|
||||
{
|
||||
this->requestFn = requestFn;
|
||||
this->type = type;
|
||||
XSetSelectionOwner(this->display, XA_PRIMARY , this->window, CurrentTime);
|
||||
XSetSelectionOwner(this->display, this->aSelection, this->window, CurrentTime);
|
||||
XFlush(this->display);
|
||||
}
|
||||
|
||||
static void x11_cb_release()
|
||||
{
|
||||
this->requestFn = NULL;
|
||||
XSetSelectionOwner(this->display, XA_PRIMARY , None, CurrentTime);
|
||||
XSetSelectionOwner(this->display, this->aSelection, None, CurrentTime);
|
||||
XFlush(this->display);
|
||||
}
|
||||
|
||||
static void x11_cb_request(LG_ClipboardData type)
|
||||
{
|
||||
if (this->aCurSelection == BadValue)
|
||||
return;
|
||||
|
||||
XConvertSelection(
|
||||
this->display,
|
||||
this->aCurSelection,
|
||||
this->aTypes[type],
|
||||
this->aSelData,
|
||||
this->window,
|
||||
CurrentTime);
|
||||
}
|
||||
|
||||
const LG_Clipboard LGC_X11 =
|
||||
{
|
||||
.getName = x11_cb_getName,
|
||||
.init = x11_cb_init,
|
||||
.free = x11_cb_free,
|
||||
.wmevent = x11_cb_wmevent,
|
||||
.notice = x11_cb_notice,
|
||||
.release = x11_cb_release,
|
||||
.request = x11_cb_request
|
||||
};
|
||||
@@ -32,11 +32,3 @@ function(make_object out_var)
|
||||
set(${out_var}_OBJS "${result}" PARENT_SCOPE)
|
||||
set(${out_var}_INCS "${result_h}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(make_defines in_file out_file)
|
||||
add_custom_command(OUTPUT ${out_file}
|
||||
COMMAND grep "^#define" "${in_file}" > "${out_file}"
|
||||
DEPENDS ${in_file}
|
||||
COMMENT "Creating #defines from ${in_file}"
|
||||
)
|
||||
endfunction()
|
||||
|
||||
25
client/decoders/CMakeLists.txt
Normal file
25
client/decoders/CMakeLists.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(decoders LANGUAGES C)
|
||||
|
||||
#find_package(PkgConfig)
|
||||
#pkg_check_modules(DECODERS_PKGCONFIG REQUIRED
|
||||
#)
|
||||
|
||||
add_library(decoders STATIC
|
||||
src/null.c
|
||||
src/yuv420.c
|
||||
)
|
||||
|
||||
target_link_libraries(decoders
|
||||
lg_common
|
||||
${DECODERS_PKGCONFIG_LIBRARIES}
|
||||
)
|
||||
|
||||
target_include_directories(decoders
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
PRIVATE
|
||||
src
|
||||
${DECODERS_PKGCONFIG_INCLUDE_DIRS}
|
||||
)
|
||||
981
client/decoders/src/h264.c
Normal file
981
client/decoders/src/h264.c
Normal file
@@ -0,0 +1,981 @@
|
||||
/*
|
||||
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 "lg-decoder.h"
|
||||
|
||||
#include "debug.h"
|
||||
#include "memcpySSE.h"
|
||||
#include "parsers/nal.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <SDL2/SDL_syswm.h>
|
||||
#include <va/va_glx.h>
|
||||
|
||||
#define SURFACE_NUM 3
|
||||
|
||||
struct Inst
|
||||
{
|
||||
LG_RendererFormat format;
|
||||
SDL_Window * window;
|
||||
VADisplay vaDisplay;
|
||||
int vaMajorVer, vaMinorVer;
|
||||
VASurfaceID vaSurfaceID[SURFACE_NUM];
|
||||
VAConfigID vaConfigID;
|
||||
VAContextID vaContextID;
|
||||
int lastSID;
|
||||
int currentSID;
|
||||
VAPictureH264 curPic;
|
||||
VAPictureH264 oldPic;
|
||||
int frameNum;
|
||||
int fieldCount;
|
||||
VABufferID picBufferID[SURFACE_NUM];
|
||||
VABufferID matBufferID[SURFACE_NUM];
|
||||
VABufferID sliBufferID[SURFACE_NUM];
|
||||
VABufferID datBufferID[SURFACE_NUM];
|
||||
bool t2First;
|
||||
int sliceType;
|
||||
|
||||
NAL nal;
|
||||
};
|
||||
|
||||
static const unsigned char MatrixBufferH264[] = {
|
||||
//ScalingList4x4[6][16]
|
||||
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
|
||||
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
|
||||
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
|
||||
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
|
||||
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
|
||||
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
|
||||
//ScalingList8x8[2][64]
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
|
||||
};
|
||||
|
||||
static bool lgd_h264_create (void ** opaque);
|
||||
static void lgd_h264_destroy (void * opaque);
|
||||
static bool lgd_h264_initialize (void * opaque, const LG_RendererFormat format, SDL_Window * window);
|
||||
static void lgd_h264_deinitialize (void * opaque);
|
||||
static LG_OutFormat lgd_h264_get_out_format (void * opaque);
|
||||
static unsigned int lgd_h264_get_frame_pitch (void * opaque);
|
||||
static unsigned int lgd_h264_get_frame_stride(void * opaque);
|
||||
static bool lgd_h264_decode (void * opaque, const uint8_t * src, size_t srcSize);
|
||||
static bool lgd_h264_get_buffer (void * opaque, uint8_t * dst, size_t dstSize);
|
||||
|
||||
static bool lgd_h264_init_gl_texture (void * opaque, GLenum target, GLuint texture, void ** ref);
|
||||
static void lgd_h264_free_gl_texture (void * opaque, void * ref);
|
||||
static bool lgd_h264_update_gl_texture(void * opaque, void * ref);
|
||||
|
||||
#define check_surface(x, y, z) _check_surface(__LINE__, x, y, z)
|
||||
static bool _check_surface(const unsigned int line, struct Inst * this, unsigned int sid, VASurfaceStatus *out)
|
||||
{
|
||||
VASurfaceStatus surfStatus;
|
||||
VAStatus status = vaQuerySurfaceStatus(
|
||||
this->vaDisplay,
|
||||
this->vaSurfaceID[sid],
|
||||
&surfStatus
|
||||
);
|
||||
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaQuerySurfaceStatus: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
#if 0
|
||||
DEBUG_INFO("L%d: surface %u status: %d", line, sid, surfStatus);
|
||||
#endif
|
||||
if (out)
|
||||
*out = surfStatus;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool lgd_h264_create(void ** opaque)
|
||||
{
|
||||
// create our local storage
|
||||
*opaque = malloc(sizeof(struct Inst));
|
||||
if (!*opaque)
|
||||
{
|
||||
DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct Inst));
|
||||
return false;
|
||||
}
|
||||
memset(*opaque, 0, sizeof(struct Inst));
|
||||
struct Inst * this = (struct Inst *)*opaque;
|
||||
|
||||
this->vaSurfaceID[0] = VA_INVALID_ID;
|
||||
this->vaConfigID = VA_INVALID_ID;
|
||||
this->vaContextID = VA_INVALID_ID;
|
||||
for(int i = 0; i < SURFACE_NUM; ++i)
|
||||
this->picBufferID[i] =
|
||||
this->matBufferID[i] =
|
||||
this->sliBufferID[i] =
|
||||
this->datBufferID[i] = VA_INVALID_ID;
|
||||
|
||||
if (!nal_initialize(&this->nal))
|
||||
{
|
||||
DEBUG_INFO("Failed to initialize NAL parser");
|
||||
free(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
lgd_h264_deinitialize(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void lgd_h264_destroy(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
nal_deinitialize(this->nal);
|
||||
lgd_h264_deinitialize(this);
|
||||
free(this);
|
||||
}
|
||||
|
||||
static bool lgd_h264_initialize(void * opaque, const LG_RendererFormat format, SDL_Window * window)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
lgd_h264_deinitialize(this);
|
||||
|
||||
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
|
||||
this->window = window;
|
||||
|
||||
SDL_SysWMinfo wminfo;
|
||||
SDL_VERSION(&wminfo.version);
|
||||
if (!SDL_GetWindowWMInfo(window, &wminfo))
|
||||
{
|
||||
DEBUG_ERROR("Failed to get SDL window WM Info");
|
||||
return false;
|
||||
}
|
||||
|
||||
switch(wminfo.subsystem)
|
||||
{
|
||||
case SDL_SYSWM_X11:
|
||||
this->vaDisplay = vaGetDisplayGLX(wminfo.info.x11.display);
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unsupported window subsystem");
|
||||
return false;
|
||||
}
|
||||
|
||||
VAStatus status;
|
||||
status = vaInitialize(this->vaDisplay, &this->vaMajorVer, &this->vaMinorVer);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaInitialize Failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_INFO("Vendor: %s", vaQueryVendorString(this->vaDisplay));
|
||||
|
||||
VAEntrypoint entryPoints[5];
|
||||
int entryPointCount;
|
||||
|
||||
status = vaQueryConfigEntrypoints(
|
||||
this->vaDisplay,
|
||||
VAProfileH264High,
|
||||
entryPoints,
|
||||
&entryPointCount
|
||||
);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaQueryConfigEntrypoints Failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
int ep;
|
||||
for(ep = 0; ep < entryPointCount; ++ep)
|
||||
if (entryPoints[ep] == VAEntrypointVLD)
|
||||
break;
|
||||
|
||||
if (ep == entryPointCount)
|
||||
{
|
||||
DEBUG_ERROR("Failed to find VAEntrypointVLD index");
|
||||
return false;
|
||||
}
|
||||
|
||||
VAConfigAttrib attrib;
|
||||
attrib.type = VAConfigAttribRTFormat;
|
||||
vaGetConfigAttributes(
|
||||
this->vaDisplay,
|
||||
VAProfileH264High,
|
||||
VAEntrypointVLD,
|
||||
&attrib,
|
||||
1);
|
||||
|
||||
if (!(attrib.value & VA_RT_FORMAT_YUV420))
|
||||
{
|
||||
DEBUG_ERROR("Failed to find desired YUV420 RT format");
|
||||
return false;
|
||||
}
|
||||
|
||||
status = vaCreateConfig(
|
||||
this->vaDisplay,
|
||||
VAProfileH264High,
|
||||
VAEntrypointVLD,
|
||||
&attrib,
|
||||
1,
|
||||
&this->vaConfigID);
|
||||
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaCreateConfig");
|
||||
return false;
|
||||
}
|
||||
|
||||
status = vaCreateSurfaces(
|
||||
this->vaDisplay,
|
||||
VA_RT_FORMAT_YUV420,
|
||||
this->format.width,
|
||||
this->format.height,
|
||||
this->vaSurfaceID,
|
||||
SURFACE_NUM,
|
||||
NULL,
|
||||
0
|
||||
);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaCreateSurfaces");
|
||||
return false;
|
||||
}
|
||||
|
||||
for(int i = 0; i < SURFACE_NUM; ++i)
|
||||
if (!check_surface(this, i, NULL))
|
||||
return false;
|
||||
|
||||
status = vaCreateContext(
|
||||
this->vaDisplay,
|
||||
this->vaConfigID,
|
||||
this->format.width,
|
||||
this->format.height,
|
||||
VA_PROGRESSIVE,
|
||||
this->vaSurfaceID,
|
||||
SURFACE_NUM,
|
||||
&this->vaContextID
|
||||
);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaCreateContext");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->currentSID = 0;
|
||||
this->sliceType = 2;
|
||||
this->t2First = true;
|
||||
|
||||
status = vaBeginPicture(this->vaDisplay, this->vaContextID, this->vaSurfaceID[0]);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaBeginPicture");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void lgd_h264_deinitialize(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
for(int i = 0; i < SURFACE_NUM; ++i)
|
||||
{
|
||||
if (this->picBufferID[i] != VA_INVALID_ID)
|
||||
vaDestroyBuffer(this->vaDisplay, this->picBufferID[i]);
|
||||
|
||||
if (this->matBufferID[i] != VA_INVALID_ID)
|
||||
vaDestroyBuffer(this->vaDisplay, this->matBufferID[i]);
|
||||
|
||||
if (this->sliBufferID[i] != VA_INVALID_ID)
|
||||
vaDestroyBuffer(this->vaDisplay, this->sliBufferID[i]);
|
||||
|
||||
if (this->datBufferID[i] != VA_INVALID_ID)
|
||||
vaDestroyBuffer(this->vaDisplay, this->datBufferID[i]);
|
||||
|
||||
this->picBufferID[i] =
|
||||
this->matBufferID[i] =
|
||||
this->sliBufferID[i] =
|
||||
this->datBufferID[i] = VA_INVALID_ID;
|
||||
}
|
||||
|
||||
if (this->vaSurfaceID[0] != VA_INVALID_ID)
|
||||
vaDestroySurfaces(this->vaDisplay, this->vaSurfaceID, SURFACE_NUM);
|
||||
this->vaSurfaceID[0] = VA_INVALID_ID;
|
||||
|
||||
if (this->vaContextID != VA_INVALID_ID)
|
||||
vaDestroyContext(this->vaDisplay, this->vaContextID);
|
||||
this->vaContextID = VA_INVALID_ID;
|
||||
|
||||
if (this->vaConfigID != VA_INVALID_ID)
|
||||
vaDestroyConfig(this->vaDisplay, this->vaConfigID);
|
||||
this->vaConfigID = VA_INVALID_ID;
|
||||
|
||||
if (this->vaDisplay)
|
||||
vaTerminate(this->vaDisplay);
|
||||
this->vaDisplay = NULL;
|
||||
}
|
||||
|
||||
static LG_OutFormat lgd_h264_get_out_format(void * opaque)
|
||||
{
|
||||
return LG_OUTPUT_YUV420;
|
||||
}
|
||||
|
||||
static unsigned int lgd_h264_get_frame_pitch(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
return this->format.width * 4;
|
||||
}
|
||||
|
||||
static unsigned int lgd_h264_get_frame_stride(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
return this->format.width;
|
||||
}
|
||||
|
||||
static bool get_buffer(struct Inst * this, const VABufferType type, const unsigned int size, VABufferID * buf_id)
|
||||
{
|
||||
if (*buf_id != VA_INVALID_ID)
|
||||
return true;
|
||||
|
||||
VAStatus status = vaCreateBuffer(this->vaDisplay, this->vaContextID, type, size, 1, NULL, buf_id);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create buffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!check_surface(this, this->currentSID, NULL))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool setup_pic_buffer(struct Inst * this, const NAL_SLICE * slice)
|
||||
{
|
||||
VAStatus status;
|
||||
|
||||
VABufferID * picBufferID = &this->picBufferID[this->currentSID];
|
||||
if (!get_buffer(this, VAPictureParameterBufferType, sizeof(VAPictureParameterBufferH264), picBufferID))
|
||||
{
|
||||
DEBUG_ERROR("get picBuffer failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
VAPictureParameterBufferH264 *p;
|
||||
status = vaMapBuffer(this->vaDisplay, *picBufferID, (void **)&p);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaMapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
const NAL_SPS * sps;
|
||||
if (!nal_get_sps(this->nal, &sps))
|
||||
{
|
||||
DEBUG_ERROR("nal_get_sps");
|
||||
return false;
|
||||
}
|
||||
|
||||
const NAL_PPS * pps;
|
||||
if (!nal_get_pps(this->nal, &pps))
|
||||
{
|
||||
DEBUG_ERROR("nal_get_pps");
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(p, 0, sizeof(VAPictureParameterBufferH264));
|
||||
p->picture_width_in_mbs_minus1 = sps->pic_width_in_mbs_minus1;
|
||||
p->picture_height_in_mbs_minus1 = sps->pic_height_in_map_units_minus1;
|
||||
p->bit_depth_luma_minus8 = sps->bit_depth_luma_minus8;
|
||||
p->bit_depth_chroma_minus8 = sps->bit_depth_chroma_minus8;
|
||||
p->num_ref_frames = sps->num_ref_frames;
|
||||
|
||||
p->seq_fields.value = 0;
|
||||
p->seq_fields.bits.chroma_format_idc = sps->chroma_format_idc;
|
||||
p->seq_fields.bits.residual_colour_transform_flag = sps->gaps_in_frame_num_value_allowed_flag;
|
||||
p->seq_fields.bits.frame_mbs_only_flag = sps->frame_mbs_only_flag;
|
||||
p->seq_fields.bits.mb_adaptive_frame_field_flag = sps->mb_adaptive_frame_field_flag;
|
||||
p->seq_fields.bits.direct_8x8_inference_flag = sps->direct_8x8_inference_flag;
|
||||
p->seq_fields.bits.MinLumaBiPredSize8x8 = sps->level_idc >= 31;
|
||||
p->seq_fields.bits.log2_max_frame_num_minus4 = sps->log2_max_frame_num_minus4;
|
||||
p->seq_fields.bits.pic_order_cnt_type = sps->pic_order_cnt_type;
|
||||
p->seq_fields.bits.log2_max_pic_order_cnt_lsb_minus4 = sps->log2_max_pic_order_cnt_lsb_minus4;
|
||||
p->seq_fields.bits.delta_pic_order_always_zero_flag = sps->delta_pic_order_always_zero_flag;
|
||||
|
||||
#if 0
|
||||
// these are deprecated, FMO is not supported
|
||||
p->num_slice_groups_minus1 = pps->num_slice_groups_minus1;
|
||||
p->slice_group_map_type = pps->slice_group_map_type;
|
||||
p->slice_group_change_rate_minus1 = pps->slice_group_change_rate_minus1;
|
||||
#endif
|
||||
|
||||
p->pic_init_qp_minus26 = pps->pic_init_qp_minus26;
|
||||
p->pic_init_qs_minus26 = pps->pic_init_qs_minus26;
|
||||
p->chroma_qp_index_offset = pps->chroma_qp_index_offset;
|
||||
p->second_chroma_qp_index_offset = pps->second_chroma_qp_index_offset;
|
||||
|
||||
p->pic_fields.value = 0;
|
||||
p->pic_fields.bits.entropy_coding_mode_flag = pps->entropy_coding_mode_flag;
|
||||
p->pic_fields.bits.weighted_pred_flag = pps->weighted_pred_flag;
|
||||
p->pic_fields.bits.weighted_bipred_idc = pps->weighted_bipred_idc;
|
||||
p->pic_fields.bits.transform_8x8_mode_flag = pps->transform_8x8_mode_flag;
|
||||
p->pic_fields.bits.field_pic_flag = slice->field_pic_flag;
|
||||
p->pic_fields.bits.constrained_intra_pred_flag = pps->constrained_intra_pred_flag;
|
||||
p->pic_fields.bits.pic_order_present_flag = pps->pic_order_present_flag;
|
||||
p->pic_fields.bits.deblocking_filter_control_present_flag = pps->deblocking_filter_control_present_flag;
|
||||
p->pic_fields.bits.redundant_pic_cnt_present_flag = pps->redundant_pic_cnt_present_flag;
|
||||
p->pic_fields.bits.reference_pic_flag = slice->nal_ref_idc != 0;
|
||||
|
||||
p->frame_num = slice->frame_num;
|
||||
|
||||
for(int i = 0; i < 16; ++i)
|
||||
{
|
||||
p->ReferenceFrames[i].flags = VA_PICTURE_H264_INVALID;
|
||||
p->ReferenceFrames[i].picture_id = 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
this->curPic.picture_id = this->vaSurfaceID[this->currentSID];
|
||||
this->curPic.frame_idx = p->frame_num;
|
||||
this->curPic.flags = 0;
|
||||
this->curPic.BottomFieldOrderCnt = this->fieldCount;
|
||||
this->curPic.TopFieldOrderCnt = this->fieldCount;
|
||||
memcpy(&p->CurrPic, &this->curPic, sizeof(VAPictureH264));
|
||||
|
||||
if (this->sliceType != 2)
|
||||
{
|
||||
memcpy(&p->ReferenceFrames[0], &this->oldPic, sizeof(VAPictureH264));
|
||||
p->ReferenceFrames[0].flags = 0;
|
||||
}
|
||||
|
||||
status = vaUnmapBuffer(this->vaDisplay, *picBufferID);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaUnmapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool setup_mat_buffer(struct Inst * this)
|
||||
{
|
||||
VAStatus status;
|
||||
|
||||
VABufferID * matBufferID = &this->matBufferID[this->currentSID];
|
||||
if (!get_buffer(this, VAIQMatrixBufferType, sizeof(VAIQMatrixBufferH264), matBufferID))
|
||||
{
|
||||
DEBUG_ERROR("get matBuffer failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
VAIQMatrixBufferH264 * m;
|
||||
status = vaMapBuffer(this->vaDisplay, *matBufferID, (void **)&m);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaMapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(m, MatrixBufferH264, sizeof(MatrixBufferH264));
|
||||
|
||||
status = vaUnmapBuffer(this->vaDisplay, *matBufferID);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaUnmapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void fill_pred_weight_table(
|
||||
NAL_PW_TABLE_L * list,
|
||||
uint32_t active,
|
||||
uint32_t luma_log2_weight_denom,
|
||||
uint8_t luma_weight_flag,
|
||||
short luma_weight[32],
|
||||
short luma_offset[32],
|
||||
uint32_t chroma_log2_weight_denom,
|
||||
uint8_t chroma_weight_flag,
|
||||
short chroma_weight[32][2],
|
||||
short chroma_offset[32][2]
|
||||
)
|
||||
{
|
||||
assert(active < 32);
|
||||
|
||||
for(uint32_t i = 0; i <= active; ++i)
|
||||
{
|
||||
NAL_PW_TABLE_L * l = &list[i];
|
||||
if (luma_weight_flag)
|
||||
{
|
||||
luma_weight[i] = l->luma_weight;
|
||||
luma_offset[i] = l->luma_offset;
|
||||
}
|
||||
else
|
||||
{
|
||||
luma_weight[i] = 1 << luma_log2_weight_denom;
|
||||
luma_weight[i] = 0;
|
||||
}
|
||||
|
||||
if (chroma_weight_flag)
|
||||
{
|
||||
chroma_weight[i][0] = l->chroma_weight[0];
|
||||
chroma_offset[i][0] = l->chroma_offset[0];
|
||||
chroma_weight[i][1] = l->chroma_weight[1];
|
||||
chroma_offset[i][1] = l->chroma_offset[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
chroma_weight[i][0] = 1 << chroma_log2_weight_denom;
|
||||
chroma_weight[i][0] = 0;
|
||||
chroma_weight[i][1] = 1 << chroma_log2_weight_denom;
|
||||
chroma_weight[i][1] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool setup_sli_buffer(struct Inst * this, size_t srcSize, const NAL_SLICE * slice, const size_t seek)
|
||||
{
|
||||
VAStatus status;
|
||||
|
||||
VABufferID * sliBufferID = &this->sliBufferID[this->currentSID];
|
||||
if (!get_buffer(this, VASliceParameterBufferType, sizeof(VASliceParameterBufferH264), sliBufferID))
|
||||
{
|
||||
DEBUG_ERROR("get sliBuffer failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
VASliceParameterBufferH264 * s;
|
||||
status = vaMapBuffer(this->vaDisplay, *sliBufferID, (void **)&s);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaMapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(s, 0, sizeof(VASliceParameterBufferH264));
|
||||
|
||||
s->slice_data_size = srcSize;
|
||||
s->slice_data_bit_offset = seek << 3;
|
||||
s->slice_data_flag = VA_SLICE_DATA_FLAG_ALL;
|
||||
|
||||
s->first_mb_in_slice = slice->first_mb_in_slice;
|
||||
s->slice_type = slice->slice_type;
|
||||
s->direct_spatial_mv_pred_flag = slice->direct_spatial_mv_pred_flag;
|
||||
s->num_ref_idx_l0_active_minus1 = slice->num_ref_idx_l0_active_minus1;
|
||||
s->num_ref_idx_l1_active_minus1 = slice->num_ref_idx_l1_active_minus1;
|
||||
s->cabac_init_idc = slice->cabac_init_idc;
|
||||
s->slice_qp_delta = slice->slice_qp_delta;
|
||||
s->disable_deblocking_filter_idc = slice->disable_deblocking_filter_idc;
|
||||
s->slice_alpha_c0_offset_div2 = slice->slice_alpha_c0_offset_div2;
|
||||
s->slice_beta_offset_div2 = slice->slice_beta_offset_div2;
|
||||
s->luma_log2_weight_denom = slice->pred_weight_table.luma_log2_weight_denom;
|
||||
s->chroma_log2_weight_denom = slice->pred_weight_table.chroma_log2_weight_denom;
|
||||
s->luma_weight_l0_flag = slice->pred_weight_table.luma_weight_flag [0];
|
||||
s->chroma_weight_l0_flag = slice->pred_weight_table.chroma_weight_flag[0];
|
||||
s->luma_weight_l1_flag = slice->pred_weight_table.luma_weight_flag [1];
|
||||
s->chroma_weight_l1_flag = slice->pred_weight_table.chroma_weight_flag[1];
|
||||
|
||||
//RefPicList0/1
|
||||
|
||||
fill_pred_weight_table(
|
||||
slice->pred_weight_table.l0,
|
||||
s->num_ref_idx_l0_active_minus1,
|
||||
s->luma_log2_weight_denom,
|
||||
s->luma_weight_l0_flag,
|
||||
s->luma_weight_l0,
|
||||
s->luma_offset_l0,
|
||||
s->chroma_log2_weight_denom,
|
||||
s->chroma_weight_l0_flag,
|
||||
s->chroma_weight_l0,
|
||||
s->chroma_weight_l0
|
||||
);
|
||||
|
||||
fill_pred_weight_table(
|
||||
slice->pred_weight_table.l1,
|
||||
s->num_ref_idx_l1_active_minus1,
|
||||
s->luma_log2_weight_denom,
|
||||
s->luma_weight_l1_flag,
|
||||
s->luma_weight_l1,
|
||||
s->luma_offset_l1,
|
||||
s->chroma_log2_weight_denom,
|
||||
s->chroma_weight_l1_flag,
|
||||
s->chroma_weight_l1,
|
||||
s->chroma_weight_l1
|
||||
);
|
||||
|
||||
#if 0
|
||||
if (this->sliceType == 2)
|
||||
{
|
||||
set_slice_parameter_buffer_t2(s, this->t2First);
|
||||
this->t2First = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
set_slice_parameter_buffer(s);
|
||||
memcpy(&s->RefPicList0[0], &this->oldPic, sizeof(VAPictureH264));
|
||||
s->RefPicList0[0].flags = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
status = vaUnmapBuffer(this->vaDisplay, *sliBufferID);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaUnmapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool setup_dat_buffer(struct Inst * this, const uint8_t * src, size_t srcSize)
|
||||
{
|
||||
VAStatus status;
|
||||
|
||||
VABufferID * datBufferID = &this->datBufferID[this->currentSID];
|
||||
|
||||
if (!get_buffer(this, VASliceDataBufferType, srcSize, datBufferID))
|
||||
{
|
||||
DEBUG_ERROR("get datBuffer failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t * d;
|
||||
status = vaMapBuffer(this->vaDisplay, *datBufferID, (void **)&d);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaMapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpySSE(d, src, srcSize);
|
||||
|
||||
status = vaUnmapBuffer(this->vaDisplay, *datBufferID);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaUnmapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool lgd_h264_decode(void * opaque, const uint8_t * src, size_t srcSize)
|
||||
{
|
||||
VAStatus status;
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
size_t seek;
|
||||
if (!nal_parse(this->nal, src, srcSize, &seek))
|
||||
{
|
||||
DEBUG_WARN("nal_parse, perhaps mid stream");
|
||||
return true;
|
||||
}
|
||||
|
||||
const NAL_SLICE * slice;
|
||||
if (!nal_get_slice(this->nal, &slice))
|
||||
{
|
||||
DEBUG_WARN("nal_get_slice failed");
|
||||
return true;
|
||||
}
|
||||
|
||||
assert(seek < srcSize);
|
||||
this->sliceType = slice->slice_type;
|
||||
|
||||
// don't start until we have an I-FRAME
|
||||
if (this->frameNum == 0 && this->sliceType != NAL_SLICE_TYPE_I)
|
||||
return true;
|
||||
|
||||
{
|
||||
if (!setup_pic_buffer(this, slice)) return false;
|
||||
if (!setup_mat_buffer(this)) return false;
|
||||
|
||||
VABufferID bufferIDs[] =
|
||||
{
|
||||
this->picBufferID[this->currentSID],
|
||||
this->matBufferID[this->currentSID]
|
||||
};
|
||||
|
||||
status = vaRenderPicture(this->vaDisplay, this->vaContextID, bufferIDs, 2);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaRenderPicture: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
// intel broke the ABI here, see:
|
||||
// https://github.com/01org/libva/commit/3eb038aa13bdd785808286c0a4995bd7a1ef07e9
|
||||
// the buffers are released by vaRenderPicture in old versions
|
||||
if (this->vaMajorVer == 0 && this->vaMinorVer < 40)
|
||||
{
|
||||
this->picBufferID[this->currentSID] =
|
||||
this->matBufferID[this->currentSID] = VA_INVALID_ID;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if (!setup_sli_buffer(this, srcSize, slice, seek)) return false;
|
||||
if (!setup_dat_buffer(this, src, srcSize )) return false;
|
||||
VABufferID bufferIDs[] =
|
||||
{
|
||||
this->sliBufferID[this->currentSID],
|
||||
this->datBufferID[this->currentSID]
|
||||
};
|
||||
|
||||
status = vaRenderPicture(this->vaDisplay, this->vaContextID, bufferIDs, 2);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaRenderPicture: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
// intel broke the ABI here, see:
|
||||
// https://github.com/01org/libva/commit/3eb038aa13bdd785808286c0a4995bd7a1ef07e9
|
||||
// the buffers are released by vaRenderPicture in old versions
|
||||
if (this->vaMajorVer == 0 && this->vaMinorVer < 40)
|
||||
{
|
||||
this->sliBufferID[this->currentSID] =
|
||||
this->datBufferID[this->currentSID] = VA_INVALID_ID;
|
||||
}
|
||||
}
|
||||
|
||||
status = vaEndPicture(this->vaDisplay, this->vaContextID);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaEndPicture: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
// advance to the next surface and save the old picture info
|
||||
this->lastSID = this->currentSID;
|
||||
if (++this->currentSID == SURFACE_NUM)
|
||||
this->currentSID = 0;
|
||||
this->frameNum += 1;
|
||||
this->fieldCount += 2;
|
||||
memcpy(&this->oldPic, &this->curPic, sizeof(VAPictureH264));
|
||||
|
||||
// prepare the next surface
|
||||
status = vaBeginPicture(this->vaDisplay, this->vaContextID, this->vaSurfaceID[this->currentSID]);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaBeginPicture: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool lgd_h264_get_buffer(void * opaque, uint8_t * dst, size_t dstSize)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
VAStatus status;
|
||||
|
||||
// don't return anything until we have some data
|
||||
if (this->frameNum == 0)
|
||||
return true;
|
||||
|
||||
// ensure the surface is ready
|
||||
status = vaSyncSurface(this->vaDisplay, this->vaSurfaceID[this->lastSID]);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaSyncSurface: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
#if 0
|
||||
// this doesn't work on my system, seems the vdpau va driver is bugged
|
||||
VASurfaceStatus surfStatus;
|
||||
if (!check_surface(this, this->lastSID, &surfStatus))
|
||||
return false;
|
||||
|
||||
if (surfStatus != VASurfaceReady)
|
||||
{
|
||||
DEBUG_ERROR("vaSyncSurface didn't block, the surface is not ready!");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// get the decoded data
|
||||
VAImage decoded =
|
||||
{
|
||||
.image_id = VA_INVALID_ID,
|
||||
.buf = VA_INVALID_ID
|
||||
};
|
||||
|
||||
status = vaDeriveImage(this->vaDisplay, this->vaSurfaceID[this->lastSID], &decoded);
|
||||
if (status == VA_STATUS_ERROR_OPERATION_FAILED)
|
||||
{
|
||||
VAImageFormat format =
|
||||
{
|
||||
.fourcc = VA_FOURCC_NV12,
|
||||
.byte_order = VA_LSB_FIRST,
|
||||
.bits_per_pixel = 12
|
||||
};
|
||||
|
||||
status = vaCreateImage(
|
||||
this->vaDisplay,
|
||||
&format,
|
||||
this->format.width,
|
||||
this->format.height,
|
||||
&decoded
|
||||
);
|
||||
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaCreateImage: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
status = vaPutImage(
|
||||
this->vaDisplay,
|
||||
this->vaSurfaceID[this->lastSID],
|
||||
decoded.image_id,
|
||||
0 , 0 ,
|
||||
this->format.width, this->format.height,
|
||||
0 , 0 ,
|
||||
this->format.width, this->format.height
|
||||
);
|
||||
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
vaDestroyImage(this->vaDisplay, decoded.image_id);
|
||||
DEBUG_ERROR("vaPutImage: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaDeriveImage: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t * d;
|
||||
status = vaMapBuffer(this->vaDisplay, decoded.buf, (void **)&d);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
vaDestroyImage(this->vaDisplay, decoded.image_id);
|
||||
DEBUG_ERROR("vaMapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpySSE(dst, d, decoded.data_size);
|
||||
|
||||
status = vaUnmapBuffer(this->vaDisplay, decoded.buf);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
vaDestroyImage(this->vaDisplay, decoded.image_id);
|
||||
DEBUG_ERROR("vaUnmapBuffer: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
status = vaDestroyImage(this->vaDisplay, decoded.image_id);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaDestroyImage: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool lgd_h264_init_gl_texture(void * opaque, GLenum target, GLuint texture, void ** ref)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
VAStatus status;
|
||||
|
||||
status = vaCreateSurfaceGLX(this->vaDisplay, target, texture, ref);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
*ref = NULL;
|
||||
DEBUG_ERROR("vaCreateSurfaceGLX: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void lgd_h264_free_gl_texture(void * opaque, void * ref)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
VAStatus status;
|
||||
|
||||
status = vaDestroySurfaceGLX(this->vaDisplay, ref);
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
DEBUG_ERROR("vaDestroySurfaceGLX: %s", vaErrorStr(status));
|
||||
}
|
||||
|
||||
static bool lgd_h264_update_gl_texture(void * opaque, void * ref)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
VAStatus status;
|
||||
|
||||
// don't return anything until we have some data
|
||||
if (this->frameNum == 0)
|
||||
return true;
|
||||
|
||||
status = vaCopySurfaceGLX(
|
||||
this->vaDisplay,
|
||||
ref,
|
||||
this->vaSurfaceID[this->lastSID],
|
||||
0
|
||||
);
|
||||
|
||||
if (status != VA_STATUS_SUCCESS)
|
||||
{
|
||||
DEBUG_ERROR("vaCopySurfaceGLX: %s", vaErrorStr(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const LG_Decoder LGD_H264 =
|
||||
{
|
||||
.name = "H.264",
|
||||
.create = lgd_h264_create,
|
||||
.destroy = lgd_h264_destroy,
|
||||
.initialize = lgd_h264_initialize,
|
||||
.deinitialize = lgd_h264_deinitialize,
|
||||
.get_out_format = lgd_h264_get_out_format,
|
||||
.get_frame_pitch = lgd_h264_get_frame_pitch,
|
||||
.get_frame_stride = lgd_h264_get_frame_stride,
|
||||
.decode = lgd_h264_decode,
|
||||
.get_buffer = lgd_h264_get_buffer,
|
||||
|
||||
.has_gl = true,
|
||||
.init_gl_texture = lgd_h264_init_gl_texture,
|
||||
.free_gl_texture = lgd_h264_free_gl_texture,
|
||||
.update_gl_texture = lgd_h264_update_gl_texture
|
||||
};
|
||||
130
client/decoders/src/null.c
Normal file
130
client/decoders/src/null.c
Normal file
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
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/decoder.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/memcpySSE.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
struct Inst
|
||||
{
|
||||
LG_RendererFormat format;
|
||||
const uint8_t * src;
|
||||
};
|
||||
|
||||
static bool lgd_null_create (void ** opaque);
|
||||
static void lgd_null_destroy (void * opaque);
|
||||
static bool lgd_null_initialize (void * opaque, const LG_RendererFormat format, SDL_Window * window);
|
||||
static void lgd_null_deinitialize (void * opaque);
|
||||
static LG_OutFormat lgd_null_get_out_format (void * opaque);
|
||||
static unsigned int lgd_null_get_frame_pitch (void * opaque);
|
||||
static unsigned int lgd_null_get_frame_stride(void * opaque);
|
||||
static bool lgd_null_decode (void * opaque, const uint8_t * src, size_t srcSize);
|
||||
static const uint8_t * lgd_null_get_buffer (void * opaque);
|
||||
|
||||
static bool lgd_null_create(void ** opaque)
|
||||
{
|
||||
// create our local storage
|
||||
*opaque = malloc(sizeof(struct Inst));
|
||||
if (!*opaque)
|
||||
{
|
||||
DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct Inst));
|
||||
return false;
|
||||
}
|
||||
memset(*opaque, 0, sizeof(struct Inst));
|
||||
return true;
|
||||
}
|
||||
|
||||
static void lgd_null_destroy(void * opaque)
|
||||
{
|
||||
free(opaque);
|
||||
}
|
||||
|
||||
static bool lgd_null_initialize(void * opaque, const LG_RendererFormat format, SDL_Window * window)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
|
||||
return true;
|
||||
}
|
||||
|
||||
static void lgd_null_deinitialize(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
memset(this, 0, sizeof(struct Inst));
|
||||
}
|
||||
|
||||
static LG_OutFormat lgd_null_get_out_format(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
switch(this->format.type)
|
||||
{
|
||||
case FRAME_TYPE_BGRA : return LG_OUTPUT_BGRA;
|
||||
case FRAME_TYPE_RGBA : return LG_OUTPUT_RGBA;
|
||||
case FRAME_TYPE_RGBA10: return LG_OUTPUT_RGBA10;
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unknown frame type");
|
||||
return LG_OUTPUT_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned int lgd_null_get_frame_pitch(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
return this->format.pitch;
|
||||
}
|
||||
|
||||
static unsigned int lgd_null_get_frame_stride(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
return this->format.stride;
|
||||
}
|
||||
|
||||
static bool lgd_null_decode(void * opaque, const uint8_t * src, size_t srcSize)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
this->src = src;
|
||||
return true;
|
||||
}
|
||||
|
||||
static const uint8_t * lgd_null_get_buffer(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (!this->src)
|
||||
return NULL;
|
||||
|
||||
return this->src;
|
||||
}
|
||||
|
||||
const LG_Decoder LGD_NULL =
|
||||
{
|
||||
.name = "NULL",
|
||||
.create = lgd_null_create,
|
||||
.destroy = lgd_null_destroy,
|
||||
.initialize = lgd_null_initialize,
|
||||
.deinitialize = lgd_null_deinitialize,
|
||||
.get_out_format = lgd_null_get_out_format,
|
||||
.get_frame_pitch = lgd_null_get_frame_pitch,
|
||||
.get_frame_stride = lgd_null_get_frame_stride,
|
||||
.decode = lgd_null_decode,
|
||||
.get_buffer = lgd_null_get_buffer
|
||||
};
|
||||
169
client/decoders/src/yuv420.c
Normal file
169
client/decoders/src/yuv420.c
Normal file
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
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/decoder.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/memcpySSE.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <GL/gl.h>
|
||||
|
||||
struct Pixel
|
||||
{
|
||||
uint8_t b, g, r, a;
|
||||
};
|
||||
|
||||
struct Inst
|
||||
{
|
||||
LG_RendererFormat format;
|
||||
struct Pixel * pixels;
|
||||
unsigned int yBytes;
|
||||
};
|
||||
|
||||
static bool lgd_yuv420_create (void ** opaque);
|
||||
static void lgd_yuv420_destroy (void * opaque);
|
||||
static bool lgd_yuv420_initialize (void * opaque, const LG_RendererFormat format, SDL_Window * window);
|
||||
static void lgd_yuv420_deinitialize (void * opaque);
|
||||
static LG_OutFormat lgd_yuv420_get_out_format (void * opaque);
|
||||
static unsigned int lgd_yuv420_get_frame_pitch (void * opaque);
|
||||
static unsigned int lgd_yuv420_get_frame_stride(void * opaque);
|
||||
static bool lgd_yuv420_decode (void * opaque, const uint8_t * src, size_t srcSize);
|
||||
static const uint8_t * lgd_yuv420_get_buffer (void * opaque);
|
||||
|
||||
static bool lgd_yuv420_create(void ** opaque)
|
||||
{
|
||||
// create our local storage
|
||||
*opaque = malloc(sizeof(struct Inst));
|
||||
if (!*opaque)
|
||||
{
|
||||
DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct Inst));
|
||||
return false;
|
||||
}
|
||||
memset(*opaque, 0, sizeof(struct Inst));
|
||||
return true;
|
||||
}
|
||||
|
||||
static void lgd_yuv420_destroy(void * opaque)
|
||||
{
|
||||
free(opaque);
|
||||
}
|
||||
|
||||
static bool lgd_yuv420_initialize(void * opaque, const LG_RendererFormat format, SDL_Window * window)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
|
||||
|
||||
this->yBytes = format.width * format.height;
|
||||
this->pixels = malloc(sizeof(struct Pixel) * (format.width * format.height));
|
||||
return true;
|
||||
}
|
||||
|
||||
static void lgd_yuv420_deinitialize(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
free(this->pixels);
|
||||
}
|
||||
|
||||
static LG_OutFormat lgd_yuv420_get_out_format(void * opaque)
|
||||
{
|
||||
return LG_OUTPUT_BGRA;
|
||||
}
|
||||
|
||||
static unsigned int lgd_yuv420_get_frame_pitch(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
return this->format.width * 4;
|
||||
}
|
||||
|
||||
static unsigned int lgd_yuv420_get_frame_stride(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
return this->format.width;
|
||||
}
|
||||
|
||||
static bool lgd_yuv420_decode(void * opaque, const uint8_t * src, size_t srcSize)
|
||||
{
|
||||
//FIXME: implement this properly using GLSL
|
||||
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
const unsigned int hw = this->format.width / 2;
|
||||
const unsigned int hp = this->yBytes / 4;
|
||||
|
||||
for(size_t y = 0; y < this->format.height; ++y)
|
||||
for(size_t x = 0; x < this->format.width; ++x)
|
||||
{
|
||||
const unsigned int yoff = y * this->format.width + x;
|
||||
const unsigned int uoff = this->yBytes + ((y / 2) * hw + x / 2);
|
||||
const unsigned int voff = uoff + hp;
|
||||
|
||||
float b = 1.164f * ((float)src[yoff] - 16.0f) + 2.018f * ((float)src[uoff] - 128.0f);
|
||||
float g = 1.164f * ((float)src[yoff] - 16.0f) - 0.813f * ((float)src[voff] - 128.0f) - 0.391f * ((float)src[uoff] - 128.0f);
|
||||
float r = 1.164f * ((float)src[yoff] - 16.0f) + 1.596f * ((float)src[voff] - 128.0f);
|
||||
|
||||
#define CLAMP(x) (x < 0 ? 0 : (x > 255 ? 255 : x))
|
||||
this->pixels[yoff].b = CLAMP(b);
|
||||
this->pixels[yoff].g = CLAMP(g);
|
||||
this->pixels[yoff].r = CLAMP(r);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static const uint8_t * lgd_yuv420_get_buffer(void * opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
return (uint8_t *)this->pixels;
|
||||
}
|
||||
|
||||
bool lgd_yuv420_init_gl_texture(void * opaque, GLenum target, GLuint texture, void ** ref)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void lgd_yuv420_free_gl_texture(void * opaque, void * ref)
|
||||
{
|
||||
}
|
||||
|
||||
bool lgd_yuv420_update_gl_texture(void * opaque, void * ref)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const LG_Decoder LGD_YUV420 =
|
||||
{
|
||||
.name = "YUV420",
|
||||
.create = lgd_yuv420_create,
|
||||
.destroy = lgd_yuv420_destroy,
|
||||
.initialize = lgd_yuv420_initialize,
|
||||
.deinitialize = lgd_yuv420_deinitialize,
|
||||
.get_out_format = lgd_yuv420_get_out_format,
|
||||
.get_frame_pitch = lgd_yuv420_get_frame_pitch,
|
||||
.get_frame_stride = lgd_yuv420_get_frame_stride,
|
||||
.decode = lgd_yuv420_decode,
|
||||
.get_buffer = lgd_yuv420_get_buffer,
|
||||
|
||||
.has_gl = false, //FIXME: Implement this
|
||||
.init_gl_texture = lgd_yuv420_init_gl_texture,
|
||||
.free_gl_texture = lgd_yuv420_free_gl_texture,
|
||||
.update_gl_texture = lgd_yuv420_update_gl_texture
|
||||
};
|
||||
@@ -1,52 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(displayservers LANGUAGES C)
|
||||
|
||||
set(DISPLAYSERVER_H "${CMAKE_BINARY_DIR}/include/dynamic/displayservers.h")
|
||||
set(DISPLAYSERVER_C "${CMAKE_BINARY_DIR}/src/displayservers.c")
|
||||
|
||||
file(WRITE ${DISPLAYSERVER_H} "#include \"interface/displayserver.h\"\n\n")
|
||||
file(APPEND ${DISPLAYSERVER_H} "extern struct LG_DisplayServerOps * LG_DisplayServers[];\n\n")
|
||||
|
||||
file(WRITE ${DISPLAYSERVER_C} "#include \"interface/displayserver.h\"\n\n")
|
||||
file(APPEND ${DISPLAYSERVER_C} "#include <stddef.h>\n\n")
|
||||
|
||||
set(DISPLAYSERVERS "_")
|
||||
set(DISPLAYSERVERS_LINK "_")
|
||||
function(add_displayserver name)
|
||||
set(DISPLAYSERVERS "${DISPLAYSERVERS};${name}" PARENT_SCOPE)
|
||||
set(DISPLAYSERVERS_LINK "${DISPLAYSERVERS_LINK};displayserver_${name}" PARENT_SCOPE)
|
||||
add_subdirectory(${name})
|
||||
endfunction()
|
||||
|
||||
# Add/remove displayservers here!
|
||||
if (ENABLE_WAYLAND)
|
||||
add_displayserver(Wayland)
|
||||
endif()
|
||||
|
||||
if (ENABLE_X11)
|
||||
add_displayserver(X11)
|
||||
endif()
|
||||
|
||||
# SDL must be last as it's the fallback implemntation
|
||||
if (ENABLE_SDL)
|
||||
add_displayserver(SDL)
|
||||
endif()
|
||||
|
||||
list(REMOVE_AT DISPLAYSERVERS 0)
|
||||
list(REMOVE_AT DISPLAYSERVERS_LINK 0)
|
||||
|
||||
list(LENGTH DISPLAYSERVERS DISPLAYSERVER_COUNT)
|
||||
file(APPEND ${DISPLAYSERVER_H} "#define LG_DISPLAYSERVER_COUNT ${DISPLAYSERVER_COUNT}\n")
|
||||
|
||||
foreach(displayserver ${DISPLAYSERVERS})
|
||||
file(APPEND ${DISPLAYSERVER_C} "extern struct LG_DisplayServerOps LGDS_${displayserver};\n")
|
||||
endforeach()
|
||||
|
||||
file(APPEND ${DISPLAYSERVER_C} "\nconst struct LG_DisplayServerOps * LG_DisplayServers[] =\n{\n")
|
||||
foreach(displayserver ${DISPLAYSERVERS})
|
||||
file(APPEND ${DISPLAYSERVER_C} " &LGDS_${displayserver},\n")
|
||||
endforeach()
|
||||
file(APPEND ${DISPLAYSERVER_C} " NULL\n};")
|
||||
|
||||
add_library(displayservers STATIC ${DISPLAYSERVER_C})
|
||||
target_link_libraries(displayservers ${DISPLAYSERVERS_LINK})
|
||||
@@ -1,24 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(displayserver_SDL LANGUAGES C)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(DISPLAYSERVER_SDL_PKGCONFIG REQUIRED
|
||||
sdl2
|
||||
)
|
||||
|
||||
add_library(displayserver_SDL STATIC
|
||||
sdl.c
|
||||
)
|
||||
|
||||
target_link_libraries(displayserver_SDL
|
||||
${DISPLAYSERVER_SDL_PKGCONFIG_LIBRARIES}
|
||||
${DISPLAYSERVER_SDL_OPT_PKGCONFIG_LIBRARIES}
|
||||
lg_common
|
||||
)
|
||||
|
||||
target_include_directories(displayserver_SDL
|
||||
PRIVATE
|
||||
src
|
||||
${DISPLAYSERVER_SDL_PKGCONFIG_INCLUDE_DIRS}
|
||||
${DISPLAYSERVER_SDL_OPT_PKGCONFIG_INCLUDE_DIRS}
|
||||
)
|
||||
@@ -1,197 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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 <linux/input.h>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
//FIXME: this should be made static once config.c is no longer using SDL
|
||||
//scancodes
|
||||
uint32_t sdl_to_xfree86[SDL_NUM_SCANCODES] =
|
||||
{
|
||||
[SDL_SCANCODE_UNKNOWN] /* = USB 0 */ = KEY_RESERVED,
|
||||
[SDL_SCANCODE_A] /* = USB 4 */ = KEY_A,
|
||||
[SDL_SCANCODE_B] /* = USB 5 */ = KEY_B,
|
||||
[SDL_SCANCODE_C] /* = USB 6 */ = KEY_C,
|
||||
[SDL_SCANCODE_D] /* = USB 7 */ = KEY_D,
|
||||
[SDL_SCANCODE_E] /* = USB 8 */ = KEY_E,
|
||||
[SDL_SCANCODE_F] /* = USB 9 */ = KEY_F,
|
||||
[SDL_SCANCODE_G] /* = USB 10 */ = KEY_G,
|
||||
[SDL_SCANCODE_H] /* = USB 11 */ = KEY_H,
|
||||
[SDL_SCANCODE_I] /* = USB 12 */ = KEY_I,
|
||||
[SDL_SCANCODE_J] /* = USB 13 */ = KEY_J,
|
||||
[SDL_SCANCODE_K] /* = USB 14 */ = KEY_K,
|
||||
[SDL_SCANCODE_L] /* = USB 15 */ = KEY_L,
|
||||
[SDL_SCANCODE_M] /* = USB 16 */ = KEY_M,
|
||||
[SDL_SCANCODE_N] /* = USB 17 */ = KEY_N,
|
||||
[SDL_SCANCODE_O] /* = USB 18 */ = KEY_O,
|
||||
[SDL_SCANCODE_P] /* = USB 19 */ = KEY_P,
|
||||
[SDL_SCANCODE_Q] /* = USB 20 */ = KEY_Q,
|
||||
[SDL_SCANCODE_R] /* = USB 21 */ = KEY_R,
|
||||
[SDL_SCANCODE_S] /* = USB 22 */ = KEY_S,
|
||||
[SDL_SCANCODE_T] /* = USB 23 */ = KEY_T,
|
||||
[SDL_SCANCODE_U] /* = USB 24 */ = KEY_U,
|
||||
[SDL_SCANCODE_V] /* = USB 25 */ = KEY_V,
|
||||
[SDL_SCANCODE_W] /* = USB 26 */ = KEY_W,
|
||||
[SDL_SCANCODE_X] /* = USB 27 */ = KEY_X,
|
||||
[SDL_SCANCODE_Y] /* = USB 28 */ = KEY_Y,
|
||||
[SDL_SCANCODE_Z] /* = USB 29 */ = KEY_Z,
|
||||
[SDL_SCANCODE_1] /* = USB 30 */ = KEY_1,
|
||||
[SDL_SCANCODE_2] /* = USB 31 */ = KEY_2,
|
||||
[SDL_SCANCODE_3] /* = USB 32 */ = KEY_3,
|
||||
[SDL_SCANCODE_4] /* = USB 33 */ = KEY_4,
|
||||
[SDL_SCANCODE_5] /* = USB 34 */ = KEY_5,
|
||||
[SDL_SCANCODE_6] /* = USB 35 */ = KEY_6,
|
||||
[SDL_SCANCODE_7] /* = USB 36 */ = KEY_7,
|
||||
[SDL_SCANCODE_8] /* = USB 37 */ = KEY_8,
|
||||
[SDL_SCANCODE_9] /* = USB 38 */ = KEY_9,
|
||||
[SDL_SCANCODE_0] /* = USB 39 */ = KEY_0,
|
||||
[SDL_SCANCODE_RETURN] /* = USB 40 */ = KEY_ENTER,
|
||||
[SDL_SCANCODE_ESCAPE] /* = USB 41 */ = KEY_ESC,
|
||||
[SDL_SCANCODE_BACKSPACE] /* = USB 42 */ = KEY_BACKSPACE,
|
||||
[SDL_SCANCODE_TAB] /* = USB 43 */ = KEY_TAB,
|
||||
[SDL_SCANCODE_SPACE] /* = USB 44 */ = KEY_SPACE,
|
||||
[SDL_SCANCODE_MINUS] /* = USB 45 */ = KEY_MINUS,
|
||||
[SDL_SCANCODE_EQUALS] /* = USB 46 */ = KEY_EQUAL,
|
||||
[SDL_SCANCODE_LEFTBRACKET] /* = USB 47 */ = KEY_LEFTBRACE,
|
||||
[SDL_SCANCODE_RIGHTBRACKET] /* = USB 48 */ = KEY_RIGHTBRACE,
|
||||
[SDL_SCANCODE_BACKSLASH] /* = USB 49 */ = KEY_BACKSLASH,
|
||||
[SDL_SCANCODE_SEMICOLON] /* = USB 51 */ = KEY_SEMICOLON,
|
||||
[SDL_SCANCODE_APOSTROPHE] /* = USB 52 */ = KEY_APOSTROPHE,
|
||||
[SDL_SCANCODE_GRAVE] /* = USB 53 */ = KEY_GRAVE,
|
||||
[SDL_SCANCODE_COMMA] /* = USB 54 */ = KEY_COMMA,
|
||||
[SDL_SCANCODE_PERIOD] /* = USB 55 */ = KEY_DOT,
|
||||
[SDL_SCANCODE_SLASH] /* = USB 56 */ = KEY_SLASH,
|
||||
[SDL_SCANCODE_CAPSLOCK] /* = USB 57 */ = KEY_CAPSLOCK,
|
||||
[SDL_SCANCODE_F1] /* = USB 58 */ = KEY_F1,
|
||||
[SDL_SCANCODE_F2] /* = USB 59 */ = KEY_F2,
|
||||
[SDL_SCANCODE_F3] /* = USB 60 */ = KEY_F3,
|
||||
[SDL_SCANCODE_F4] /* = USB 61 */ = KEY_F4,
|
||||
[SDL_SCANCODE_F5] /* = USB 62 */ = KEY_F5,
|
||||
[SDL_SCANCODE_F6] /* = USB 63 */ = KEY_F6,
|
||||
[SDL_SCANCODE_F7] /* = USB 64 */ = KEY_F7,
|
||||
[SDL_SCANCODE_F8] /* = USB 65 */ = KEY_F8,
|
||||
[SDL_SCANCODE_F9] /* = USB 66 */ = KEY_F9,
|
||||
[SDL_SCANCODE_F10] /* = USB 67 */ = KEY_F10,
|
||||
[SDL_SCANCODE_F11] /* = USB 68 */ = KEY_F11,
|
||||
[SDL_SCANCODE_F12] /* = USB 69 */ = KEY_F12,
|
||||
[SDL_SCANCODE_PRINTSCREEN] /* = USB 70 */ = KEY_SYSRQ,
|
||||
[SDL_SCANCODE_SCROLLLOCK] /* = USB 71 */ = KEY_SCROLLLOCK,
|
||||
[SDL_SCANCODE_PAUSE] /* = USB 72 */ = KEY_PAUSE,
|
||||
[SDL_SCANCODE_INSERT] /* = USB 73 */ = KEY_INSERT,
|
||||
[SDL_SCANCODE_HOME] /* = USB 74 */ = KEY_HOME,
|
||||
[SDL_SCANCODE_PAGEUP] /* = USB 75 */ = KEY_PAGEUP,
|
||||
[SDL_SCANCODE_DELETE] /* = USB 76 */ = KEY_DELETE,
|
||||
[SDL_SCANCODE_END] /* = USB 77 */ = KEY_END,
|
||||
[SDL_SCANCODE_PAGEDOWN] /* = USB 78 */ = KEY_PAGEDOWN,
|
||||
[SDL_SCANCODE_RIGHT] /* = USB 79 */ = KEY_RIGHT,
|
||||
[SDL_SCANCODE_LEFT] /* = USB 80 */ = KEY_LEFT,
|
||||
[SDL_SCANCODE_DOWN] /* = USB 81 */ = KEY_DOWN,
|
||||
[SDL_SCANCODE_UP] /* = USB 82 */ = KEY_UP,
|
||||
[SDL_SCANCODE_NUMLOCKCLEAR] /* = USB 83 */ = KEY_NUMLOCK,
|
||||
[SDL_SCANCODE_KP_DIVIDE] /* = USB 84 */ = KEY_KPSLASH,
|
||||
[SDL_SCANCODE_KP_MULTIPLY] /* = USB 85 */ = KEY_KPASTERISK,
|
||||
[SDL_SCANCODE_KP_MINUS] /* = USB 86 */ = KEY_KPMINUS,
|
||||
[SDL_SCANCODE_KP_PLUS] /* = USB 87 */ = KEY_KPPLUS,
|
||||
[SDL_SCANCODE_KP_ENTER] /* = USB 88 */ = KEY_KPENTER,
|
||||
[SDL_SCANCODE_KP_1] /* = USB 89 */ = KEY_KP1,
|
||||
[SDL_SCANCODE_KP_2] /* = USB 90 */ = KEY_KP2,
|
||||
[SDL_SCANCODE_KP_3] /* = USB 91 */ = KEY_KP3,
|
||||
[SDL_SCANCODE_KP_4] /* = USB 92 */ = KEY_KP4,
|
||||
[SDL_SCANCODE_KP_5] /* = USB 93 */ = KEY_KP5,
|
||||
[SDL_SCANCODE_KP_6] /* = USB 94 */ = KEY_KP6,
|
||||
[SDL_SCANCODE_KP_7] /* = USB 95 */ = KEY_KP7,
|
||||
[SDL_SCANCODE_KP_8] /* = USB 96 */ = KEY_KP8,
|
||||
[SDL_SCANCODE_KP_9] /* = USB 97 */ = KEY_KP9,
|
||||
[SDL_SCANCODE_KP_0] /* = USB 98 */ = KEY_KP0,
|
||||
[SDL_SCANCODE_KP_PERIOD] /* = USB 99 */ = KEY_KPDOT,
|
||||
[SDL_SCANCODE_NONUSBACKSLASH] /* = USB 100 */ = KEY_102ND,
|
||||
[SDL_SCANCODE_APPLICATION] /* = USB 101 */ = KEY_COMPOSE,
|
||||
[SDL_SCANCODE_POWER] /* = USB 102 */ = KEY_POWER,
|
||||
[SDL_SCANCODE_KP_EQUALS] /* = USB 103 */ = KEY_KPEQUAL,
|
||||
[SDL_SCANCODE_F13] /* = USB 104 */ = KEY_CONFIG,
|
||||
[SDL_SCANCODE_F14] /* = USB 105 */ = KEY_F14,
|
||||
[SDL_SCANCODE_F15] /* = USB 106 */ = KEY_F15,
|
||||
[SDL_SCANCODE_F16] /* = USB 107 */ = KEY_F16,
|
||||
[SDL_SCANCODE_F17] /* = USB 108 */ = KEY_F17,
|
||||
[SDL_SCANCODE_F18] /* = USB 109 */ = KEY_F18,
|
||||
[SDL_SCANCODE_F19] /* = USB 110 */ = KEY_F19,
|
||||
[SDL_SCANCODE_F20] /* = USB 111 */ = KEY_F20,
|
||||
[SDL_SCANCODE_HELP] /* = USB 117 */ = KEY_HELP,
|
||||
[SDL_SCANCODE_MENU] /* = USB 118 */ = KEY_MENU,
|
||||
[SDL_SCANCODE_STOP] /* = USB 120 */ = KEY_CANCEL,
|
||||
[SDL_SCANCODE_AGAIN] /* = USB 121 */ = KEY_AGAIN,
|
||||
[SDL_SCANCODE_UNDO] /* = USB 122 */ = KEY_UNDO,
|
||||
[SDL_SCANCODE_CUT] /* = USB 123 */ = KEY_CUT,
|
||||
[SDL_SCANCODE_COPY] /* = USB 124 */ = KEY_COPY,
|
||||
[SDL_SCANCODE_PASTE] /* = USB 125 */ = KEY_PASTE,
|
||||
[SDL_SCANCODE_FIND] /* = USB 126 */ = KEY_FIND,
|
||||
[SDL_SCANCODE_MUTE] /* = USB 127 */ = KEY_MUTE,
|
||||
[SDL_SCANCODE_VOLUMEUP] /* = USB 128 */ = KEY_VOLUMEUP,
|
||||
[SDL_SCANCODE_VOLUMEDOWN] /* = USB 129 */ = KEY_VOLUMEDOWN,
|
||||
[SDL_SCANCODE_KP_COMMA] /* = USB 133 */ = KEY_KPCOMMA,
|
||||
[SDL_SCANCODE_INTERNATIONAL1] /* = USB 135 */ = KEY_RO,
|
||||
[SDL_SCANCODE_INTERNATIONAL2] /* = USB 136 */ = KEY_KATAKANAHIRAGANA,
|
||||
[SDL_SCANCODE_INTERNATIONAL3] /* = USB 137 */ = KEY_YEN,
|
||||
[SDL_SCANCODE_INTERNATIONAL4] /* = USB 138 */ = KEY_HENKAN,
|
||||
[SDL_SCANCODE_INTERNATIONAL5] /* = USB 139 */ = KEY_MUHENKAN,
|
||||
[SDL_SCANCODE_LANG1] /* = USB 144 */ = KEY_HANGEUL,
|
||||
[SDL_SCANCODE_LANG2] /* = USB 145 */ = KEY_HANJA,
|
||||
[SDL_SCANCODE_SYSREQ] /* = USB 154 */ = KEY_RIGHTSHIFT,
|
||||
[SDL_SCANCODE_CANCEL] /* = USB 155 */ = KEY_STOP,
|
||||
[SDL_SCANCODE_KP_LEFTPAREN] /* = USB 182 */ = KEY_KPLEFTPAREN,
|
||||
[SDL_SCANCODE_KP_RIGHTPAREN] /* = USB 183 */ = KEY_KPRIGHTPAREN,
|
||||
[SDL_SCANCODE_KP_PLUSMINUS] /* = USB 215 */ = KEY_KPPLUSMINUS,
|
||||
[SDL_SCANCODE_LCTRL] /* = USB 224 */ = KEY_LEFTCTRL,
|
||||
[SDL_SCANCODE_LSHIFT] /* = USB 225 */ = KEY_LEFTSHIFT,
|
||||
[SDL_SCANCODE_LALT] /* = USB 226 */ = KEY_LEFTALT,
|
||||
[SDL_SCANCODE_LGUI] /* = USB 227 */ = KEY_LEFTMETA,
|
||||
[SDL_SCANCODE_RCTRL] /* = USB 228 */ = KEY_RIGHTCTRL,
|
||||
[SDL_SCANCODE_RSHIFT] /* = USB 229 */ = KEY_RIGHTSHIFT,
|
||||
[SDL_SCANCODE_RALT] /* = USB 230 */ = KEY_RIGHTALT,
|
||||
[SDL_SCANCODE_RGUI] /* = USB 231 */ = KEY_RIGHTMETA,
|
||||
[SDL_SCANCODE_MODE] /* = USB 257 */ = KEY_ZENKAKUHANKAKU,
|
||||
[SDL_SCANCODE_AUDIONEXT] /* = USB 258 */ = KEY_NEXTSONG,
|
||||
[SDL_SCANCODE_AUDIOPREV] /* = USB 259 */ = KEY_PREVIOUSSONG,
|
||||
[SDL_SCANCODE_AUDIOSTOP] /* = USB 260 */ = KEY_STOPCD,
|
||||
[SDL_SCANCODE_AUDIOPLAY] /* = USB 261 */ = KEY_PLAYPAUSE,
|
||||
[SDL_SCANCODE_MEDIASELECT] /* = USB 263 */ = KEY_MEDIA,
|
||||
[SDL_SCANCODE_WWW] /* = USB 264 */ = KEY_WWW,
|
||||
[SDL_SCANCODE_MAIL] /* = USB 265 */ = KEY_MAIL,
|
||||
[SDL_SCANCODE_CALCULATOR] /* = USB 266 */ = KEY_CALC,
|
||||
[SDL_SCANCODE_COMPUTER] /* = USB 267 */ = KEY_COMPUTER,
|
||||
[SDL_SCANCODE_AC_SEARCH] /* = USB 268 */ = KEY_SEARCH,
|
||||
[SDL_SCANCODE_AC_HOME] /* = USB 269 */ = KEY_HOMEPAGE,
|
||||
[SDL_SCANCODE_AC_BACK] /* = USB 270 */ = KEY_BACK,
|
||||
[SDL_SCANCODE_AC_FORWARD] /* = USB 271 */ = KEY_FORWARD,
|
||||
[SDL_SCANCODE_AC_REFRESH] /* = USB 273 */ = KEY_REFRESH,
|
||||
[SDL_SCANCODE_AC_BOOKMARKS] /* = USB 274 */ = KEY_BOOKMARKS,
|
||||
[SDL_SCANCODE_BRIGHTNESSDOWN] /* = USB 275 */ = KEY_BRIGHTNESSDOWN,
|
||||
[SDL_SCANCODE_BRIGHTNESSUP] /* = USB 276 */ = KEY_BRIGHTNESSUP,
|
||||
[SDL_SCANCODE_DISPLAYSWITCH] /* = USB 277 */ = KEY_SWITCHVIDEOMODE,
|
||||
[SDL_SCANCODE_KBDILLUMTOGGLE] /* = USB 278 */ = KEY_KBDILLUMTOGGLE,
|
||||
[SDL_SCANCODE_KBDILLUMDOWN] /* = USB 279 */ = KEY_KBDILLUMDOWN,
|
||||
[SDL_SCANCODE_KBDILLUMUP] /* = USB 280 */ = KEY_KBDILLUMUP,
|
||||
[SDL_SCANCODE_EJECT] /* = USB 281 */ = KEY_EJECTCD,
|
||||
[SDL_SCANCODE_SLEEP] /* = USB 282 */ = KEY_SLEEP,
|
||||
[SDL_SCANCODE_APP1] /* = USB 283 */ = KEY_PROG1,
|
||||
[SDL_SCANCODE_APP2] /* = USB 284 */ = KEY_PROG2,
|
||||
[SDL_SCANCODE_AUDIOREWIND] /* = USB 285 */ = KEY_REWIND,
|
||||
[SDL_SCANCODE_AUDIOFASTFORWARD] /* = USB 286 */ = KEY_FASTFORWARD,
|
||||
};
|
||||
@@ -1,561 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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/displayserver.h"
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_syswm.h>
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
#include <EGL/eglext.h>
|
||||
#endif
|
||||
|
||||
#if defined(SDL_VIDEO_DRIVER_WAYLAND)
|
||||
#include <wayland-egl.h>
|
||||
#endif
|
||||
|
||||
#include "app.h"
|
||||
#include "kb.h"
|
||||
#include "egl_dynprocs.h"
|
||||
#include "common/types.h"
|
||||
#include "common/debug.h"
|
||||
#include "util.h"
|
||||
|
||||
struct SDLDSState
|
||||
{
|
||||
SDL_Window * window;
|
||||
SDL_Cursor * cursor;
|
||||
|
||||
EGLNativeWindowType wlDisplay;
|
||||
|
||||
bool keyboardGrabbed;
|
||||
bool pointerGrabbed;
|
||||
bool exiting;
|
||||
};
|
||||
|
||||
static struct SDLDSState sdl;
|
||||
|
||||
/* forwards */
|
||||
static int sdlEventFilter(void * userdata, SDL_Event * event);
|
||||
|
||||
static void sdlSetup(void)
|
||||
{
|
||||
}
|
||||
|
||||
static bool sdlProbe(void)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool sdlEarlyInit(void)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool sdlInit(const LG_DSInitParams params)
|
||||
{
|
||||
memset(&sdl, 0, sizeof(sdl));
|
||||
|
||||
// Allow screensavers for now: we will enable and disable as needed.
|
||||
SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1");
|
||||
|
||||
if (SDL_Init(SDL_INIT_VIDEO) < 0)
|
||||
{
|
||||
DEBUG_ERROR("SDL_Init Failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
if (params.opengl)
|
||||
{
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER , 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4);
|
||||
SDL_GL_SetAttribute(SDL_GL_RED_SIZE , 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE , 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE , 8);
|
||||
}
|
||||
#endif
|
||||
|
||||
sdl.window = SDL_CreateWindow(
|
||||
params.title,
|
||||
params.center ? SDL_WINDOWPOS_CENTERED : params.x,
|
||||
params.center ? SDL_WINDOWPOS_CENTERED : params.y,
|
||||
params.w,
|
||||
params.h,
|
||||
(
|
||||
SDL_WINDOW_HIDDEN |
|
||||
(params.resizable ? SDL_WINDOW_RESIZABLE : 0) |
|
||||
(params.borderless ? SDL_WINDOW_BORDERLESS : 0) |
|
||||
(params.maximize ? SDL_WINDOW_MAXIMIZED : 0) |
|
||||
(params.opengl ? SDL_WINDOW_OPENGL : 0)
|
||||
)
|
||||
);
|
||||
|
||||
if (sdl.window == NULL)
|
||||
{
|
||||
DEBUG_ERROR("Could not create an SDL window: %s\n", SDL_GetError());
|
||||
goto fail_init;
|
||||
}
|
||||
|
||||
const uint8_t data[4] = {0xf, 0x9, 0x9, 0xf};
|
||||
const uint8_t mask[4] = {0xf, 0xf, 0xf, 0xf};
|
||||
sdl.cursor = SDL_CreateCursor(data, mask, 8, 4, 4, 0);
|
||||
SDL_SetCursor(sdl.cursor);
|
||||
|
||||
SDL_ShowWindow(sdl.window);
|
||||
|
||||
SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
|
||||
|
||||
if (params.fullscreen)
|
||||
SDL_SetWindowFullscreen(sdl.window, SDL_WINDOW_FULLSCREEN_DESKTOP);
|
||||
|
||||
if (!params.center)
|
||||
SDL_SetWindowPosition(sdl.window, params.x, params.y);
|
||||
|
||||
// ensure mouse acceleration is identical in server mode
|
||||
SDL_SetHintWithPriority(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, "1", SDL_HINT_OVERRIDE);
|
||||
|
||||
SDL_SetEventFilter(sdlEventFilter, NULL);
|
||||
return true;
|
||||
|
||||
fail_init:
|
||||
SDL_Quit();
|
||||
return false;
|
||||
}
|
||||
|
||||
static void sdlStartup(void)
|
||||
{
|
||||
}
|
||||
|
||||
static void sdlShutdown(void)
|
||||
{
|
||||
}
|
||||
|
||||
static void sdlFree(void)
|
||||
{
|
||||
SDL_DestroyWindow(sdl.window);
|
||||
|
||||
if (sdl.cursor)
|
||||
SDL_FreeCursor(sdl.cursor);
|
||||
|
||||
if (sdl.window)
|
||||
SDL_DestroyWindow(sdl.window);
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
static bool sdlGetProp(LG_DSProperty prop, void * ret)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
static EGLDisplay sdlGetEGLDisplay(void)
|
||||
{
|
||||
SDL_SysWMinfo wminfo;
|
||||
SDL_VERSION(&wminfo.version);
|
||||
if (!SDL_GetWindowWMInfo(sdl.window, &wminfo))
|
||||
{
|
||||
DEBUG_ERROR("SDL_GetWindowWMInfo failed");
|
||||
return EGL_NO_DISPLAY;
|
||||
}
|
||||
|
||||
EGLNativeDisplayType native;
|
||||
EGLenum platform;
|
||||
|
||||
switch(wminfo.subsystem)
|
||||
{
|
||||
case SDL_SYSWM_X11:
|
||||
native = (EGLNativeDisplayType)wminfo.info.x11.display;
|
||||
platform = EGL_PLATFORM_X11_KHR;
|
||||
break;
|
||||
|
||||
#if defined(SDL_VIDEO_DRIVER_WAYLAND)
|
||||
case SDL_SYSWM_WAYLAND:
|
||||
native = (EGLNativeDisplayType)wminfo.info.wl.display;
|
||||
platform = EGL_PLATFORM_WAYLAND_KHR;
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unsupported subsystem");
|
||||
return EGL_NO_DISPLAY;
|
||||
}
|
||||
|
||||
const char *early_exts = eglQueryString(NULL, EGL_EXTENSIONS);
|
||||
|
||||
if (util_hasGLExt(early_exts, "EGL_KHR_platform_base") &&
|
||||
g_egl_dynProcs.eglGetPlatformDisplay)
|
||||
{
|
||||
DEBUG_INFO("Using eglGetPlatformDisplay");
|
||||
return g_egl_dynProcs.eglGetPlatformDisplay(platform, native, NULL);
|
||||
}
|
||||
|
||||
if (util_hasGLExt(early_exts, "EGL_EXT_platform_base") &&
|
||||
g_egl_dynProcs.eglGetPlatformDisplayEXT)
|
||||
{
|
||||
DEBUG_INFO("Using eglGetPlatformDisplayEXT");
|
||||
return g_egl_dynProcs.eglGetPlatformDisplayEXT(platform, native, NULL);
|
||||
}
|
||||
|
||||
DEBUG_INFO("Using eglGetDisplay");
|
||||
return eglGetDisplay(native);
|
||||
}
|
||||
|
||||
static EGLNativeWindowType sdlGetEGLNativeWindow(void)
|
||||
{
|
||||
SDL_SysWMinfo wminfo;
|
||||
SDL_VERSION(&wminfo.version);
|
||||
if (!SDL_GetWindowWMInfo(sdl.window, &wminfo))
|
||||
{
|
||||
DEBUG_ERROR("SDL_GetWindowWMInfo failed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch(wminfo.subsystem)
|
||||
{
|
||||
case SDL_SYSWM_X11:
|
||||
return (EGLNativeWindowType)wminfo.info.x11.window;
|
||||
|
||||
#if defined(SDL_VIDEO_DRIVER_WAYLAND)
|
||||
case SDL_SYSWM_WAYLAND:
|
||||
{
|
||||
if (sdl.wlDisplay)
|
||||
return sdl.wlDisplay;
|
||||
|
||||
int width, height;
|
||||
SDL_GetWindowSize(sdl.window, &width, &height);
|
||||
sdl.wlDisplay = (EGLNativeWindowType)wl_egl_window_create(
|
||||
wminfo.info.wl.surface, width, height);
|
||||
|
||||
return sdl.wlDisplay;
|
||||
}
|
||||
#endif
|
||||
|
||||
default:
|
||||
DEBUG_ERROR("Unsupported subsystem");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void sdlEGLSwapBuffers(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count)
|
||||
{
|
||||
eglSwapBuffers(display, surface);
|
||||
}
|
||||
#endif //ENABLE_EGL
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
static LG_DSGLContext sdlGLCreateContext(void)
|
||||
{
|
||||
return (LG_DSGLContext)SDL_GL_CreateContext(sdl.window);
|
||||
}
|
||||
|
||||
static void sdlGLDeleteContext(LG_DSGLContext context)
|
||||
{
|
||||
SDL_GL_DeleteContext((SDL_GLContext)context);
|
||||
}
|
||||
|
||||
static void sdlGLMakeCurrent(LG_DSGLContext context)
|
||||
{
|
||||
SDL_GL_MakeCurrent(sdl.window, (SDL_GLContext)context);
|
||||
}
|
||||
|
||||
static void sdlGLSetSwapInterval(int interval)
|
||||
{
|
||||
SDL_GL_SetSwapInterval(interval);
|
||||
}
|
||||
|
||||
static void sdlGLSwapBuffers(void)
|
||||
{
|
||||
SDL_GL_SwapWindow(sdl.window);
|
||||
}
|
||||
#endif //ENABLE_OPENGL
|
||||
|
||||
static int sdlEventFilter(void * userdata, SDL_Event * event)
|
||||
{
|
||||
switch(event->type)
|
||||
{
|
||||
case SDL_QUIT:
|
||||
app_handleCloseEvent();
|
||||
break;
|
||||
|
||||
case SDL_MOUSEMOTION:
|
||||
// stop motion events during the warp out of the window
|
||||
if (sdl.exiting)
|
||||
break;
|
||||
|
||||
app_updateCursorPos(event->motion.x, event->motion.y);
|
||||
app_handleMouseRelative(event->motion.xrel, event->motion.yrel,
|
||||
event->motion.xrel, event->motion.yrel);
|
||||
break;
|
||||
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
{
|
||||
int button = event->button.button;
|
||||
if (button > 3)
|
||||
button += 2;
|
||||
|
||||
app_handleButtonPress(button);
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
{
|
||||
int button = event->button.button;
|
||||
if (button > 3)
|
||||
button += 2;
|
||||
|
||||
app_handleButtonRelease(button);
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_MOUSEWHEEL:
|
||||
{
|
||||
int button = event->wheel.y > 0 ? 4 : 5;
|
||||
app_handleButtonPress(button);
|
||||
app_handleButtonRelease(button);
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_KEYDOWN:
|
||||
{
|
||||
SDL_Scancode sc = event->key.keysym.scancode;
|
||||
app_handleKeyPress(sdl_to_xfree86[sc]);
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_KEYUP:
|
||||
{
|
||||
SDL_Scancode sc = event->key.keysym.scancode;
|
||||
app_handleKeyRelease(sdl_to_xfree86[sc]);
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_WINDOWEVENT:
|
||||
switch(event->window.event)
|
||||
{
|
||||
case SDL_WINDOWEVENT_ENTER:
|
||||
app_handleEnterEvent(true);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_LEAVE:
|
||||
sdl.exiting = false;
|
||||
app_handleEnterEvent(false);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_FOCUS_GAINED:
|
||||
app_handleFocusEvent(true);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_FOCUS_LOST:
|
||||
app_handleFocusEvent(false);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||
case SDL_WINDOWEVENT_RESIZED:
|
||||
{
|
||||
struct Border border;
|
||||
SDL_GetWindowBordersSize(
|
||||
sdl.window,
|
||||
&border.top,
|
||||
&border.left,
|
||||
&border.bottom,
|
||||
&border.right
|
||||
);
|
||||
app_handleResizeEvent(
|
||||
event->window.data1,
|
||||
event->window.data2,
|
||||
1,
|
||||
border);
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_WINDOWEVENT_MOVED:
|
||||
app_updateWindowPos(event->window.data1, event->window.data2);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_CLOSE:
|
||||
app_handleCloseEvent();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sdlShowPointer(bool show)
|
||||
{
|
||||
SDL_ShowCursor(show ? SDL_ENABLE : SDL_DISABLE);
|
||||
}
|
||||
|
||||
static void sdlGrabPointer(void)
|
||||
{
|
||||
SDL_SetWindowGrab(sdl.window, SDL_TRUE);
|
||||
SDL_SetRelativeMouseMode(SDL_TRUE);
|
||||
sdl.pointerGrabbed = true;
|
||||
}
|
||||
|
||||
static void sdlUngrabPointer(void)
|
||||
{
|
||||
SDL_SetWindowGrab(sdl.window, SDL_FALSE);
|
||||
SDL_SetRelativeMouseMode(SDL_FALSE);
|
||||
sdl.pointerGrabbed = false;
|
||||
}
|
||||
|
||||
static void sdlGrabKeyboard(void)
|
||||
{
|
||||
if (sdl.pointerGrabbed)
|
||||
SDL_SetWindowGrab(sdl.window, SDL_FALSE);
|
||||
else
|
||||
{
|
||||
DEBUG_WARN("SDL does not support grabbing only the keyboard, grabbing all");
|
||||
sdl.pointerGrabbed = true;
|
||||
}
|
||||
|
||||
SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "1");
|
||||
SDL_SetWindowGrab(sdl.window, SDL_TRUE);
|
||||
sdl.keyboardGrabbed = true;
|
||||
}
|
||||
|
||||
static void sdlUngrabKeyboard(void)
|
||||
{
|
||||
SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "0");
|
||||
SDL_SetWindowGrab(sdl.window, SDL_FALSE);
|
||||
if (sdl.pointerGrabbed)
|
||||
SDL_SetWindowGrab(sdl.window, SDL_TRUE);
|
||||
sdl.keyboardGrabbed = false;
|
||||
}
|
||||
|
||||
static void sdlWarpPointer(int x, int y, bool exiting)
|
||||
{
|
||||
if (sdl.exiting)
|
||||
return;
|
||||
|
||||
sdl.exiting = exiting;
|
||||
|
||||
// if exiting turn off relative mode
|
||||
if (exiting)
|
||||
SDL_SetRelativeMouseMode(SDL_FALSE);
|
||||
|
||||
// issue the warp
|
||||
SDL_WarpMouseInWindow(sdl.window, x, y);
|
||||
}
|
||||
|
||||
static void sdlRealignPointer(void)
|
||||
{
|
||||
app_handleMouseRelative(0.0, 0.0, 0.0, 0.0);
|
||||
}
|
||||
|
||||
static bool sdlIsValidPointerPos(int x, int y)
|
||||
{
|
||||
const int displays = SDL_GetNumVideoDisplays();
|
||||
for(int i = 0; i < displays; ++i)
|
||||
{
|
||||
SDL_Rect r;
|
||||
SDL_GetDisplayBounds(i, &r);
|
||||
if ((x >= r.x && x < r.x + r.w) &&
|
||||
(y >= r.y && y < r.y + r.h))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void sdlInhibitIdle(void)
|
||||
{
|
||||
SDL_DisableScreenSaver();
|
||||
}
|
||||
|
||||
static void sdlUninhibitIdle(void)
|
||||
{
|
||||
SDL_EnableScreenSaver();
|
||||
}
|
||||
|
||||
static void sdlWait(unsigned int time)
|
||||
{
|
||||
SDL_WaitEventTimeout(NULL, time);
|
||||
}
|
||||
|
||||
static void sdlSetWindowSize(int x, int y)
|
||||
{
|
||||
SDL_SetWindowSize(sdl.window, x, y);
|
||||
}
|
||||
|
||||
static void sdlSetFullscreen(bool fs)
|
||||
{
|
||||
SDL_SetWindowFullscreen(sdl.window, fs ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
|
||||
}
|
||||
|
||||
static bool sdlGetFullscreen(void)
|
||||
{
|
||||
return (SDL_GetWindowFlags(sdl.window) & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0;
|
||||
}
|
||||
|
||||
static void sdlMinimize(void)
|
||||
{
|
||||
SDL_MinimizeWindow(sdl.window);
|
||||
}
|
||||
|
||||
struct LG_DisplayServerOps LGDS_SDL =
|
||||
{
|
||||
.setup = sdlSetup,
|
||||
.probe = sdlProbe,
|
||||
.earlyInit = sdlEarlyInit,
|
||||
.init = sdlInit,
|
||||
.startup = sdlStartup,
|
||||
.shutdown = sdlShutdown,
|
||||
.free = sdlFree,
|
||||
.getProp = sdlGetProp,
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
.getEGLDisplay = sdlGetEGLDisplay,
|
||||
.getEGLNativeWindow = sdlGetEGLNativeWindow,
|
||||
.eglSwapBuffers = sdlEGLSwapBuffers,
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
.glCreateContext = sdlGLCreateContext,
|
||||
.glDeleteContext = sdlGLDeleteContext,
|
||||
.glMakeCurrent = sdlGLMakeCurrent,
|
||||
.glSetSwapInterval = sdlGLSetSwapInterval,
|
||||
.glSwapBuffers = sdlGLSwapBuffers,
|
||||
#endif
|
||||
|
||||
.showPointer = sdlShowPointer,
|
||||
.grabPointer = sdlGrabPointer,
|
||||
.ungrabPointer = sdlUngrabPointer,
|
||||
.capturePointer = sdlGrabPointer,
|
||||
.uncapturePointer = sdlUngrabPointer,
|
||||
.grabKeyboard = sdlGrabKeyboard,
|
||||
.ungrabKeyboard = sdlUngrabKeyboard,
|
||||
.warpPointer = sdlWarpPointer,
|
||||
.realignPointer = sdlRealignPointer,
|
||||
.isValidPointerPos = sdlIsValidPointerPos,
|
||||
.inhibitIdle = sdlInhibitIdle,
|
||||
.uninhibitIdle = sdlUninhibitIdle,
|
||||
.wait = sdlWait,
|
||||
.setWindowSize = sdlSetWindowSize,
|
||||
.setFullscreen = sdlSetFullscreen,
|
||||
.getFullscreen = sdlGetFullscreen,
|
||||
.minimize = sdlMinimize,
|
||||
|
||||
/* SDL does not have clipboard support */
|
||||
.cbInit = NULL,
|
||||
};
|
||||
@@ -1,92 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(displayserver_Wayland LANGUAGES C)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(DISPLAYSERVER_Wayland_PKGCONFIG REQUIRED
|
||||
wayland-client
|
||||
)
|
||||
|
||||
#pkg_check_modules(DISPLAYSERVER_Wayland_OPT_PKGCONFIG
|
||||
#)
|
||||
|
||||
set(displayserver_Wayland_SHELL_SRC "")
|
||||
|
||||
if (ENABLE_LIBDECOR)
|
||||
pkg_check_modules(DISPLAYSERVER_Wayland_LIBDECOR REQUIRED
|
||||
libdecor-0.1
|
||||
)
|
||||
list(APPEND DISPLAYSERVER_Wayland_PKGCONFIG_LIBRARIES ${DISPLAYSERVER_Wayland_LIBDECOR_LIBRARIES})
|
||||
list(APPEND DISPLAYSERVER_Wayland_PKGCONFIG_INCLUDE_DIRS ${DISPLAYSERVER_Wayland_LIBDECOR_INCLUDE_DIRS})
|
||||
list(APPEND displayserver_Wayland_SHELL_SRC shell_libdecor.c)
|
||||
add_definitions(-D ENABLE_LIBDECOR)
|
||||
else()
|
||||
list(APPEND displayserver_Wayland_SHELL_SRC shell_xdg.c)
|
||||
endif()
|
||||
|
||||
add_library(displayserver_Wayland STATIC
|
||||
clipboard.c
|
||||
cursor.c
|
||||
gl.c
|
||||
idle.c
|
||||
input.c
|
||||
output.c
|
||||
poll.c
|
||||
state.c
|
||||
registry.c
|
||||
wayland.c
|
||||
window.c
|
||||
${displayserver_Wayland_SHELL_SRC}
|
||||
)
|
||||
|
||||
target_link_libraries(displayserver_Wayland
|
||||
${DISPLAYSERVER_Wayland_PKGCONFIG_LIBRARIES}
|
||||
${DISPLAYSERVER_Wayland_OPT_PKGCONFIG_LIBRARIES}
|
||||
lg_common
|
||||
)
|
||||
|
||||
target_include_directories(displayserver_Wayland
|
||||
PRIVATE
|
||||
src
|
||||
${DISPLAYSERVER_Wayland_PKGCONFIG_INCLUDE_DIRS}
|
||||
${DISPLAYSERVER_Wayland_OPT_PKGCONFIG_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
find_program(WAYLAND_SCANNER_EXECUTABLE NAMES wayland-scanner)
|
||||
pkg_check_modules(WAYLAND_PROTOCOLS REQUIRED wayland-protocols>=1.15)
|
||||
pkg_get_variable(WAYLAND_PROTOCOLS_BASE wayland-protocols pkgdatadir)
|
||||
|
||||
macro(wayland_generate protocol_file output_file)
|
||||
add_custom_command(OUTPUT "${output_file}.h"
|
||||
COMMAND "${WAYLAND_SCANNER_EXECUTABLE}" client-header "${protocol_file}" "${output_file}.h"
|
||||
DEPENDS "${protocol_file}"
|
||||
VERBATIM)
|
||||
|
||||
add_custom_command(OUTPUT "${output_file}.c"
|
||||
COMMAND "${WAYLAND_SCANNER_EXECUTABLE}" private-code "${protocol_file}" "${output_file}.c"
|
||||
DEPENDS "${protocol_file}"
|
||||
VERBATIM)
|
||||
|
||||
target_sources(displayserver_Wayland PRIVATE "${output_file}.h" "${output_file}.c")
|
||||
endmacro()
|
||||
|
||||
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/wayland")
|
||||
include_directories("${CMAKE_BINARY_DIR}/wayland")
|
||||
wayland_generate(
|
||||
"${WAYLAND_PROTOCOLS_BASE}/stable/xdg-shell/xdg-shell.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-xdg-shell-client-protocol")
|
||||
wayland_generate(
|
||||
"${WAYLAND_PROTOCOLS_BASE}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-xdg-decoration-unstable-v1-client-protocol")
|
||||
wayland_generate(
|
||||
"${WAYLAND_PROTOCOLS_BASE}/unstable/relative-pointer/relative-pointer-unstable-v1.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-relative-pointer-unstable-v1-client-protocol")
|
||||
wayland_generate(
|
||||
"${WAYLAND_PROTOCOLS_BASE}/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-pointer-constraints-unstable-v1-client-protocol")
|
||||
wayland_generate(
|
||||
"${WAYLAND_PROTOCOLS_BASE}/unstable/keyboard-shortcuts-inhibit/keyboard-shortcuts-inhibit-unstable-v1.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-keyboard-shortcuts-inhibit-unstable-v1-client-protocol")
|
||||
wayland_generate(
|
||||
"${WAYLAND_PROTOCOLS_BASE}/unstable/idle-inhibit/idle-inhibit-unstable-v1.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-idle-inhibit-unstable-v1-client-protocol")
|
||||
|
||||
@@ -1,596 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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 "wayland.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <unistd.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "app.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
struct DataOffer {
|
||||
bool isSelfCopy;
|
||||
char * mimetypes[LG_CLIPBOARD_DATA_NONE];
|
||||
};
|
||||
|
||||
static const char * textMimetypes[] =
|
||||
{
|
||||
"text/plain",
|
||||
"text/plain;charset=utf-8",
|
||||
"TEXT",
|
||||
"STRING",
|
||||
"UTF8_STRING",
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const char * pngMimetypes[] =
|
||||
{
|
||||
"image/png",
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const char * bmpMimetypes[] =
|
||||
{
|
||||
"image/bmp",
|
||||
"image/x-bmp",
|
||||
"image/x-MS-bmp",
|
||||
"image/x-win-bitmap",
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const char * tiffMimetypes[] =
|
||||
{
|
||||
"image/tiff",
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const char * jpegMimetypes[] =
|
||||
{
|
||||
"image/jpeg",
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const char ** cbTypeToMimetypes(enum LG_ClipboardData type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case LG_CLIPBOARD_DATA_TEXT:
|
||||
return textMimetypes;
|
||||
case LG_CLIPBOARD_DATA_PNG:
|
||||
return pngMimetypes;
|
||||
case LG_CLIPBOARD_DATA_BMP:
|
||||
return bmpMimetypes;
|
||||
case LG_CLIPBOARD_DATA_TIFF:
|
||||
return tiffMimetypes;
|
||||
case LG_CLIPBOARD_DATA_JPEG:
|
||||
return jpegMimetypes;
|
||||
default:
|
||||
DEBUG_ERROR("invalid clipboard type");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
static bool containsMimetype(const char ** mimetypes,
|
||||
const char * needle)
|
||||
{
|
||||
for (const char ** mimetype = mimetypes; *mimetype; mimetype++)
|
||||
if (!strcmp(needle, *mimetype))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool mimetypeEndswith(const char * mimetype, const char * what)
|
||||
{
|
||||
size_t mimetypeLen = strlen(mimetype);
|
||||
size_t whatLen = strlen(what);
|
||||
|
||||
if (mimetypeLen < whatLen)
|
||||
return false;
|
||||
|
||||
return !strcmp(mimetype + mimetypeLen - whatLen, what);
|
||||
}
|
||||
|
||||
static bool isTextMimetype(const char * mimetype)
|
||||
{
|
||||
if (containsMimetype(textMimetypes, mimetype))
|
||||
return true;
|
||||
|
||||
if (!strcmp(mimetype, "text/ico"))
|
||||
return false;
|
||||
|
||||
char * text = "text/";
|
||||
if (!strncmp(mimetype, text, strlen(text)))
|
||||
return true;
|
||||
|
||||
if (mimetypeEndswith(mimetype, "script") ||
|
||||
mimetypeEndswith(mimetype, "xml") ||
|
||||
mimetypeEndswith(mimetype, "yaml"))
|
||||
return true;
|
||||
|
||||
if (strstr(mimetype, "json"))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static enum LG_ClipboardData mimetypeToCbType(const char * mimetype)
|
||||
{
|
||||
if (isTextMimetype(mimetype))
|
||||
return LG_CLIPBOARD_DATA_TEXT;
|
||||
|
||||
if (containsMimetype(pngMimetypes, mimetype))
|
||||
return LG_CLIPBOARD_DATA_PNG;
|
||||
|
||||
if (containsMimetype(bmpMimetypes, mimetype))
|
||||
return LG_CLIPBOARD_DATA_BMP;
|
||||
|
||||
if (containsMimetype(tiffMimetypes, mimetype))
|
||||
return LG_CLIPBOARD_DATA_TIFF;
|
||||
|
||||
if (containsMimetype(jpegMimetypes, mimetype))
|
||||
return LG_CLIPBOARD_DATA_JPEG;
|
||||
|
||||
return LG_CLIPBOARD_DATA_NONE;
|
||||
}
|
||||
|
||||
static bool isImageCbtype(enum LG_ClipboardData type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case LG_CLIPBOARD_DATA_TEXT:
|
||||
return false;
|
||||
case LG_CLIPBOARD_DATA_PNG:
|
||||
case LG_CLIPBOARD_DATA_BMP:
|
||||
case LG_CLIPBOARD_DATA_TIFF:
|
||||
case LG_CLIPBOARD_DATA_JPEG:
|
||||
return true;
|
||||
default:
|
||||
DEBUG_ERROR("invalid clipboard type");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
static bool hasAnyMimetype(char ** mimetypes)
|
||||
{
|
||||
for (enum LG_ClipboardData i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
if (mimetypes[i])
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool hasImageMimetype(char ** mimetypes)
|
||||
{
|
||||
for (enum LG_ClipboardData i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
if (isImageCbtype(i) && mimetypes[i])
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Destination client handlers.
|
||||
|
||||
static void dataOfferHandleOffer(void * opaque, struct wl_data_offer * offer,
|
||||
const char * mimetype)
|
||||
{
|
||||
struct DataOffer * data = opaque;
|
||||
|
||||
if (!strcmp(mimetype, wlCb.lgMimetype))
|
||||
{
|
||||
data->isSelfCopy = true;
|
||||
return;
|
||||
}
|
||||
|
||||
enum LG_ClipboardData type = mimetypeToCbType(mimetype);
|
||||
|
||||
if (type == LG_CLIPBOARD_DATA_NONE)
|
||||
return;
|
||||
|
||||
// text/html represents rich text format, which is almost never desirable when
|
||||
// and should not be used when a plain text or image format is available.
|
||||
if ((isImageCbtype(type) || containsMimetype(textMimetypes, mimetype)) &&
|
||||
data->mimetypes[LG_CLIPBOARD_DATA_TEXT] &&
|
||||
strstr(data->mimetypes[LG_CLIPBOARD_DATA_TEXT], "html"))
|
||||
{
|
||||
free(data->mimetypes[LG_CLIPBOARD_DATA_TEXT]);
|
||||
data->mimetypes[LG_CLIPBOARD_DATA_TEXT] = NULL;
|
||||
}
|
||||
|
||||
if (strstr(mimetype, "html") && hasImageMimetype(data->mimetypes))
|
||||
return;
|
||||
|
||||
if (data->mimetypes[type])
|
||||
return;
|
||||
|
||||
data->mimetypes[type] = strdup(mimetype);
|
||||
}
|
||||
|
||||
static void dataOfferHandleSourceActions(void * data,
|
||||
struct wl_data_offer * offer, uint32_t sourceActions)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
static void dataOfferHandleAction(void * data, struct wl_data_offer * offer,
|
||||
uint32_t dndAction)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
static const struct wl_data_offer_listener dataOfferListener = {
|
||||
.offer = dataOfferHandleOffer,
|
||||
.source_actions = dataOfferHandleSourceActions,
|
||||
.action = dataOfferHandleAction,
|
||||
};
|
||||
|
||||
static void dataDeviceHandleDataOffer(void * data,
|
||||
struct wl_data_device * dataDevice, struct wl_data_offer * offer)
|
||||
{
|
||||
struct DataOffer * extra = calloc(1, sizeof(struct DataOffer));
|
||||
if (!extra)
|
||||
{
|
||||
DEBUG_ERROR("Out of memory while handling clipboard");
|
||||
abort();
|
||||
}
|
||||
wl_data_offer_set_user_data(offer, extra);
|
||||
wl_data_offer_add_listener(offer, &dataOfferListener, extra);
|
||||
}
|
||||
|
||||
static void dataDeviceHandleSelection(void * opaque,
|
||||
struct wl_data_device * dataDevice, struct wl_data_offer * offer)
|
||||
{
|
||||
if (!offer)
|
||||
{
|
||||
waylandCBInvalidate();
|
||||
return;
|
||||
}
|
||||
|
||||
struct DataOffer * extra = wl_data_offer_get_user_data(offer);
|
||||
if (!hasAnyMimetype(extra->mimetypes) || extra->isSelfCopy)
|
||||
{
|
||||
waylandCBInvalidate();
|
||||
wl_data_offer_destroy(offer);
|
||||
return;
|
||||
}
|
||||
|
||||
wlCb.offer = offer;
|
||||
|
||||
for (enum LG_ClipboardData i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
free(wlCb.mimetypes[i]);
|
||||
memcpy(wlCb.mimetypes, extra->mimetypes, sizeof(wlCb.mimetypes));
|
||||
|
||||
wl_data_offer_set_user_data(offer, NULL);
|
||||
free(extra);
|
||||
|
||||
int idx = 0;
|
||||
enum LG_ClipboardData types[LG_CLIPBOARD_DATA_NONE];
|
||||
for (enum LG_ClipboardData i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
if (wlCb.mimetypes[i])
|
||||
types[idx++] = i;
|
||||
|
||||
app_clipboardNotifyTypes(types, idx);
|
||||
}
|
||||
|
||||
static void dataDeviceHandleEnter(void * data, struct wl_data_device * device,
|
||||
uint32_t serial, struct wl_surface * surface, wl_fixed_t sxW, wl_fixed_t syW,
|
||||
struct wl_data_offer * offer)
|
||||
{
|
||||
assert(wlCb.dndOffer == NULL);
|
||||
wlCb.dndOffer = offer;
|
||||
|
||||
struct DataOffer * extra = wl_data_offer_get_user_data(offer);
|
||||
for (enum LG_ClipboardData i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
free(extra->mimetypes[i]);
|
||||
free(extra);
|
||||
|
||||
wl_data_offer_set_user_data(offer, NULL);
|
||||
wl_data_offer_set_actions(offer, WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE,
|
||||
WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE);
|
||||
}
|
||||
|
||||
static void dataDeviceHandleMotion(void * data, struct wl_data_device * device,
|
||||
uint32_t time, wl_fixed_t sxW, wl_fixed_t syW)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
static void dataDeviceHandleLeave(void * data, struct wl_data_device * device)
|
||||
{
|
||||
wl_data_offer_destroy(wlCb.dndOffer);
|
||||
wlCb.dndOffer = NULL;
|
||||
}
|
||||
|
||||
static void dataDeviceHandleDrop(void * data, struct wl_data_device * device)
|
||||
{
|
||||
wl_data_offer_destroy(wlCb.dndOffer);
|
||||
wlCb.dndOffer = NULL;
|
||||
}
|
||||
|
||||
static const struct wl_data_device_listener dataDeviceListener = {
|
||||
.data_offer = dataDeviceHandleDataOffer,
|
||||
.selection = dataDeviceHandleSelection,
|
||||
.enter = dataDeviceHandleEnter,
|
||||
.motion = dataDeviceHandleMotion,
|
||||
.leave = dataDeviceHandleLeave,
|
||||
.drop = dataDeviceHandleDrop,
|
||||
};
|
||||
|
||||
bool waylandCBInit(void)
|
||||
{
|
||||
memset(&wlCb, 0, sizeof(wlCb));
|
||||
|
||||
if (!wlWm.dataDeviceManager)
|
||||
{
|
||||
DEBUG_ERROR("Missing wl_data_device_manager interface (version 3+)");
|
||||
return false;
|
||||
}
|
||||
|
||||
wlCb.dataDevice = wl_data_device_manager_get_data_device(
|
||||
wlWm.dataDeviceManager, wlWm.seat);
|
||||
if (!wlCb.dataDevice)
|
||||
{
|
||||
DEBUG_ERROR("Failed to get data device");
|
||||
return false;
|
||||
}
|
||||
|
||||
wl_data_device_add_listener(wlCb.dataDevice, &dataDeviceListener, NULL);
|
||||
|
||||
snprintf(wlCb.lgMimetype, sizeof(wlCb.lgMimetype),
|
||||
"application/x-looking-glass-copy;pid=%d", getpid());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void clipboardReadCancel(struct ClipboardRead * data)
|
||||
{
|
||||
waylandPollUnregister(data->fd);
|
||||
close(data->fd);
|
||||
free(data->buf);
|
||||
free(data);
|
||||
wlCb.currentRead = NULL;
|
||||
}
|
||||
|
||||
static void clipboardReadCallback(uint32_t events, void * opaque)
|
||||
{
|
||||
struct ClipboardRead * data = opaque;
|
||||
if (events & EPOLLERR)
|
||||
{
|
||||
clipboardReadCancel(data);
|
||||
return;
|
||||
}
|
||||
|
||||
ssize_t result = read(data->fd, data->buf + data->numRead, data->size - data->numRead);
|
||||
if (result < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to read from clipboard: %s", strerror(errno));
|
||||
clipboardReadCancel(data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
app_clipboardNotifySize(data->type, data->numRead);
|
||||
app_clipboardData(data->type, data->buf, data->numRead);
|
||||
clipboardReadCancel(data);
|
||||
return;
|
||||
}
|
||||
|
||||
data->numRead += result;
|
||||
if (data->numRead >= data->size)
|
||||
{
|
||||
data->size *= 2;
|
||||
void * nbuf = realloc(data->buf, data->size);
|
||||
if (!nbuf) {
|
||||
DEBUG_ERROR("Failed to realloc clipboard buffer: %s", strerror(errno));
|
||||
clipboardReadCancel(data);
|
||||
return;
|
||||
}
|
||||
|
||||
data->buf = nbuf;
|
||||
}
|
||||
}
|
||||
|
||||
void waylandCBInvalidate(void)
|
||||
{
|
||||
if (wlCb.currentRead)
|
||||
clipboardReadCancel(wlCb.currentRead);
|
||||
|
||||
app_clipboardRelease();
|
||||
|
||||
if (wlCb.offer)
|
||||
wl_data_offer_destroy(wlCb.offer);
|
||||
wlCb.offer = NULL;
|
||||
}
|
||||
|
||||
void waylandCBRequest(LG_ClipboardData type)
|
||||
{
|
||||
if (!wlCb.offer || !wlCb.mimetypes[type])
|
||||
{
|
||||
app_clipboardRelease();
|
||||
return;
|
||||
}
|
||||
|
||||
if (wlCb.currentRead)
|
||||
clipboardReadCancel(wlCb.currentRead);
|
||||
|
||||
int fds[2];
|
||||
if (pipe(fds) < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to get a clipboard pipe: %s", strerror(errno));
|
||||
abort();
|
||||
}
|
||||
|
||||
wl_data_offer_receive(wlCb.offer, wlCb.mimetypes[type], fds[1]);
|
||||
close(fds[1]);
|
||||
|
||||
struct ClipboardRead * data = malloc(sizeof(struct ClipboardRead));
|
||||
if (!data)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate memory to read clipboard");
|
||||
close(fds[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
data->fd = fds[0];
|
||||
data->size = 4096;
|
||||
data->numRead = 0;
|
||||
data->buf = malloc(data->size);
|
||||
data->offer = wlCb.offer;
|
||||
data->type = type;
|
||||
|
||||
if (!data->buf)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate memory to receive clipboard data");
|
||||
close(data->fd);
|
||||
free(data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!waylandPollRegister(data->fd, clipboardReadCallback, data, EPOLLIN))
|
||||
{
|
||||
DEBUG_ERROR("Failed to register clipboard read into epoll: %s", strerror(errno));
|
||||
close(data->fd);
|
||||
free(data->buf);
|
||||
free(data);
|
||||
}
|
||||
|
||||
wlCb.currentRead = data;
|
||||
}
|
||||
|
||||
struct ClipboardWrite
|
||||
{
|
||||
int fd;
|
||||
size_t pos;
|
||||
struct CountedBuffer * buffer;
|
||||
};
|
||||
|
||||
static void clipboardWriteCallback(uint32_t events, void * opaque)
|
||||
{
|
||||
struct ClipboardWrite * data = opaque;
|
||||
if (events & EPOLLERR)
|
||||
goto error;
|
||||
|
||||
ssize_t written = write(data->fd, data->buffer->data + data->pos, data->buffer->size - data->pos);
|
||||
if (written < 0)
|
||||
{
|
||||
if (errno != EPIPE)
|
||||
DEBUG_ERROR("Failed to write clipboard data: %s", strerror(errno));
|
||||
goto error;
|
||||
}
|
||||
|
||||
data->pos += written;
|
||||
if (data->pos < data->buffer->size)
|
||||
return;
|
||||
|
||||
error:
|
||||
waylandPollUnregister(data->fd);
|
||||
close(data->fd);
|
||||
countedBufferRelease(&data->buffer);
|
||||
free(data);
|
||||
}
|
||||
|
||||
static void dataSourceHandleSend(void * data, struct wl_data_source * source,
|
||||
const char * mimetype, int fd)
|
||||
{
|
||||
struct WCBTransfer * transfer = (struct WCBTransfer *) data;
|
||||
if (containsMimetype(transfer->mimetypes, mimetype))
|
||||
{
|
||||
struct ClipboardWrite * data = malloc(sizeof(struct ClipboardWrite));
|
||||
if (!data)
|
||||
{
|
||||
DEBUG_ERROR("Out of memory trying to allocate ClipboardWrite");
|
||||
goto error;
|
||||
}
|
||||
|
||||
data->fd = fd;
|
||||
data->pos = 0;
|
||||
data->buffer = transfer->data;
|
||||
countedBufferAddRef(transfer->data);
|
||||
waylandPollRegister(fd, clipboardWriteCallback, data, EPOLLOUT);
|
||||
return;
|
||||
}
|
||||
|
||||
error:
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void dataSourceHandleCancelled(void * data,
|
||||
struct wl_data_source * source)
|
||||
{
|
||||
struct WCBTransfer * transfer = (struct WCBTransfer *) data;
|
||||
countedBufferRelease(&transfer->data);
|
||||
free(transfer);
|
||||
wl_data_source_destroy(source);
|
||||
}
|
||||
|
||||
static const struct wl_data_source_listener dataSourceListener = {
|
||||
.send = dataSourceHandleSend,
|
||||
.cancelled = dataSourceHandleCancelled,
|
||||
};
|
||||
|
||||
static void waylandCBReplyFn(void * opaque, LG_ClipboardData type,
|
||||
uint8_t * data, uint32_t size)
|
||||
{
|
||||
struct WCBTransfer * transfer = malloc(sizeof(struct WCBTransfer));
|
||||
if (!transfer)
|
||||
{
|
||||
DEBUG_ERROR("Out of memory when allocating WCBTransfer");
|
||||
return;
|
||||
}
|
||||
|
||||
transfer->mimetypes = cbTypeToMimetypes(type);
|
||||
transfer->data = countedBufferNew(size);
|
||||
if (!transfer->data)
|
||||
{
|
||||
DEBUG_ERROR("Out of memory when allocating clipboard buffer");
|
||||
free(transfer);
|
||||
return;
|
||||
}
|
||||
memcpy(transfer->data->data, data, size);
|
||||
|
||||
struct wl_data_source * source =
|
||||
wl_data_device_manager_create_data_source(wlWm.dataDeviceManager);
|
||||
wl_data_source_add_listener(source, &dataSourceListener, transfer);
|
||||
for (const char ** mimetype = transfer->mimetypes; *mimetype; mimetype++)
|
||||
wl_data_source_offer(source, *mimetype);
|
||||
wl_data_source_offer(source, wlCb.lgMimetype);
|
||||
|
||||
wl_data_device_set_selection(wlCb.dataDevice, source,
|
||||
wlWm.keyboardEnterSerial);
|
||||
}
|
||||
|
||||
void waylandCBNotice(LG_ClipboardData type)
|
||||
{
|
||||
wlCb.haveRequest = true;
|
||||
wlCb.type = type;
|
||||
app_clipboardRequest(waylandCBReplyFn, NULL);
|
||||
}
|
||||
|
||||
void waylandCBRelease(void)
|
||||
{
|
||||
wlCb.haveRequest = false;
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include "wayland.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
static const uint32_t cursorBitmap[] = {
|
||||
0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x000000, 0xFFFFFF, 0xFFFFFF, 0x000000,
|
||||
0x000000, 0xFFFFFF, 0xFFFFFF, 0x000000,
|
||||
0x000000, 0x000000, 0x000000, 0x000000,
|
||||
};
|
||||
|
||||
static struct wl_buffer * createCursorBuffer(void)
|
||||
{
|
||||
int fd = memfd_create("lg-cursor", 0);
|
||||
if (fd < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create cursor shared memory: %d", errno);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct wl_buffer * result = NULL;
|
||||
|
||||
if (ftruncate(fd, sizeof cursorBitmap) < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to ftruncate cursor shared memory: %d", errno);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
void * shm_data = mmap(NULL, sizeof cursorBitmap, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (shm_data == MAP_FAILED)
|
||||
{
|
||||
DEBUG_ERROR("Failed to map memory for cursor: %d", errno);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
struct wl_shm_pool * pool = wl_shm_create_pool(wlWm.shm, fd, sizeof cursorBitmap);
|
||||
result = wl_shm_pool_create_buffer(pool, 0, 4, 4, 16, WL_SHM_FORMAT_XRGB8888);
|
||||
wl_shm_pool_destroy(pool);
|
||||
|
||||
memcpy(shm_data, cursorBitmap, sizeof cursorBitmap);
|
||||
munmap(shm_data, sizeof cursorBitmap);
|
||||
|
||||
fail:
|
||||
close(fd);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool waylandCursorInit(void)
|
||||
{
|
||||
if (!wlWm.compositor)
|
||||
{
|
||||
DEBUG_ERROR("Compositor missing wl_compositor, will not proceed");
|
||||
return false;
|
||||
}
|
||||
|
||||
wlWm.cursorBuffer = createCursorBuffer();
|
||||
if (wlWm.cursorBuffer)
|
||||
{
|
||||
wlWm.cursor = wl_compositor_create_surface(wlWm.compositor);
|
||||
wl_surface_attach(wlWm.cursor, wlWm.cursorBuffer, 0, 0);
|
||||
wl_surface_commit(wlWm.cursor);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void waylandCursorFree(void)
|
||||
{
|
||||
if (wlWm.cursor)
|
||||
wl_surface_destroy(wlWm.cursor);
|
||||
if (wlWm.cursorBuffer)
|
||||
wl_buffer_destroy(wlWm.cursorBuffer);
|
||||
}
|
||||
|
||||
void waylandShowPointer(bool show)
|
||||
{
|
||||
wlWm.showPointer = show;
|
||||
wl_pointer_set_cursor(wlWm.pointer, wlWm.pointerEnterSerial, show ? wlWm.cursor : NULL, 0, 0);
|
||||
}
|
||||
@@ -1,217 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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 "wayland.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "app.h"
|
||||
#include "common/debug.h"
|
||||
#include "util.h"
|
||||
|
||||
#if defined(ENABLE_EGL) || defined(ENABLE_OPENGL)
|
||||
#include "egl_dynprocs.h"
|
||||
|
||||
bool waylandEGLInit(int w, int h)
|
||||
{
|
||||
wlWm.eglWindow = wl_egl_window_create(wlWm.surface, w, h);
|
||||
if (!wlWm.eglWindow)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create EGL window");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
EGLDisplay waylandGetEGLDisplay(void)
|
||||
{
|
||||
EGLNativeDisplayType native = (EGLNativeDisplayType) wlWm.display;
|
||||
|
||||
const char *early_exts = eglQueryString(NULL, EGL_EXTENSIONS);
|
||||
|
||||
if (util_hasGLExt(early_exts, "EGL_KHR_platform_wayland") &&
|
||||
g_egl_dynProcs.eglGetPlatformDisplay)
|
||||
{
|
||||
DEBUG_INFO("Using eglGetPlatformDisplay");
|
||||
return g_egl_dynProcs.eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, native, NULL);
|
||||
}
|
||||
|
||||
if (util_hasGLExt(early_exts, "EGL_EXT_platform_wayland") &&
|
||||
g_egl_dynProcs.eglGetPlatformDisplayEXT)
|
||||
{
|
||||
DEBUG_INFO("Using eglGetPlatformDisplayEXT");
|
||||
return g_egl_dynProcs.eglGetPlatformDisplayEXT(EGL_PLATFORM_WAYLAND_EXT, native, NULL);
|
||||
}
|
||||
|
||||
DEBUG_INFO("Using eglGetDisplay");
|
||||
return eglGetDisplay(native);
|
||||
}
|
||||
|
||||
void waylandEGLSwapBuffers(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count)
|
||||
{
|
||||
if (!wlWm.eglSwapWithDamageInit)
|
||||
{
|
||||
const char *exts = eglQueryString(display, EGL_EXTENSIONS);
|
||||
wlWm.eglSwapWithDamageInit = true;
|
||||
if (wl_proxy_get_version((struct wl_proxy *) wlWm.surface) < 4)
|
||||
DEBUG_INFO("Swapping buffers with damage: not supported, need wl_compositor v4");
|
||||
else if (util_hasGLExt(exts, "EGL_KHR_swap_buffers_with_damage") && g_egl_dynProcs.eglSwapBuffersWithDamageKHR)
|
||||
{
|
||||
wlWm.eglSwapWithDamage = g_egl_dynProcs.eglSwapBuffersWithDamageKHR;
|
||||
DEBUG_INFO("Using EGL_KHR_swap_buffers_with_damage");
|
||||
}
|
||||
else if (util_hasGLExt(exts, "EGL_EXT_swap_buffers_with_damage") && g_egl_dynProcs.eglSwapBuffersWithDamageEXT)
|
||||
{
|
||||
wlWm.eglSwapWithDamage = g_egl_dynProcs.eglSwapBuffersWithDamageEXT;
|
||||
DEBUG_INFO("Using EGL_EXT_swap_buffers_with_damage");
|
||||
}
|
||||
else
|
||||
DEBUG_INFO("Swapping buffers with damage: not supported");
|
||||
}
|
||||
|
||||
if (wlWm.eglSwapWithDamage && count)
|
||||
{
|
||||
if (count * 4 > wlWm.eglDamageRectCount)
|
||||
{
|
||||
free(wlWm.eglDamageRects);
|
||||
wlWm.eglDamageRects = malloc(sizeof(EGLint) * count * 4);
|
||||
if (!wlWm.eglDamageRects)
|
||||
DEBUG_FATAL("Out of memory");
|
||||
wlWm.eglDamageRectCount = count * 4;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
wlWm.eglDamageRects[i*4+0] = damage[i].x;
|
||||
wlWm.eglDamageRects[i*4+1] = damage[i].y;
|
||||
wlWm.eglDamageRects[i*4+2] = damage[i].w;
|
||||
wlWm.eglDamageRects[i*4+3] = damage[i].h;
|
||||
}
|
||||
|
||||
wlWm.eglSwapWithDamage(display, surface, wlWm.eglDamageRects, count);
|
||||
}
|
||||
else
|
||||
eglSwapBuffers(display, surface);
|
||||
|
||||
if (wlWm.needsResize)
|
||||
{
|
||||
wl_egl_window_resize(wlWm.eglWindow, wlWm.width * wlWm.scale, wlWm.height * wlWm.scale, 0, 0);
|
||||
wl_surface_set_buffer_scale(wlWm.surface, wlWm.scale);
|
||||
|
||||
struct wl_region * region = wl_compositor_create_region(wlWm.compositor);
|
||||
wl_region_add(region, 0, 0, wlWm.width, wlWm.height);
|
||||
wl_surface_set_opaque_region(wlWm.surface, region);
|
||||
wl_region_destroy(region);
|
||||
|
||||
app_handleResizeEvent(wlWm.width, wlWm.height, wlWm.scale, (struct Border) {0, 0, 0, 0});
|
||||
wlWm.needsResize = false;
|
||||
}
|
||||
|
||||
waylandShellAckConfigureIfNeeded();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
EGLNativeWindowType waylandGetEGLNativeWindow(void)
|
||||
{
|
||||
return (EGLNativeWindowType) wlWm.eglWindow;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
bool waylandOpenGLInit(void)
|
||||
{
|
||||
EGLint attr[] =
|
||||
{
|
||||
EGL_BUFFER_SIZE , 24,
|
||||
EGL_CONFORMANT , EGL_OPENGL_BIT,
|
||||
EGL_RENDERABLE_TYPE , EGL_OPENGL_BIT,
|
||||
EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER,
|
||||
EGL_RED_SIZE , 8,
|
||||
EGL_GREEN_SIZE , 8,
|
||||
EGL_BLUE_SIZE , 8,
|
||||
EGL_SAMPLE_BUFFERS , 0,
|
||||
EGL_SAMPLES , 0,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
wlWm.glDisplay = waylandGetEGLDisplay();
|
||||
|
||||
int maj, min;
|
||||
if (!eglInitialize(wlWm.glDisplay, &maj, &min))
|
||||
{
|
||||
DEBUG_ERROR("Unable to initialize EGL");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (wlWm.glDisplay == EGL_NO_DISPLAY)
|
||||
{
|
||||
DEBUG_ERROR("Failed to get EGL display (eglError: 0x%x)", eglGetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
EGLint num_config;
|
||||
if (!eglChooseConfig(wlWm.glDisplay, attr, &wlWm.glConfig, 1, &num_config))
|
||||
{
|
||||
DEBUG_ERROR("Failed to choose config (eglError: 0x%x)", eglGetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
wlWm.glSurface = eglCreateWindowSurface(wlWm.glDisplay, wlWm.glConfig, wlWm.eglWindow, NULL);
|
||||
if (wlWm.glSurface == EGL_NO_SURFACE)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create EGL surface (eglError: 0x%x)", eglGetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
LG_DSGLContext waylandGLCreateContext(void)
|
||||
{
|
||||
eglBindAPI(EGL_OPENGL_API);
|
||||
return eglCreateContext(wlWm.glDisplay, wlWm.glConfig, EGL_NO_CONTEXT, NULL);
|
||||
}
|
||||
|
||||
void waylandGLDeleteContext(LG_DSGLContext context)
|
||||
{
|
||||
eglDestroyContext(wlWm.glDisplay, context);
|
||||
}
|
||||
|
||||
void waylandGLMakeCurrent(LG_DSGLContext context)
|
||||
{
|
||||
eglMakeCurrent(wlWm.glDisplay, wlWm.glSurface, wlWm.glSurface, context);
|
||||
}
|
||||
|
||||
void waylandGLSetSwapInterval(int interval)
|
||||
{
|
||||
eglSwapInterval(wlWm.glDisplay, interval);
|
||||
}
|
||||
|
||||
void waylandGLSwapBuffers(void)
|
||||
{
|
||||
waylandEGLSwapBuffers(wlWm.glDisplay, wlWm.glSurface, NULL, 0);
|
||||
}
|
||||
#endif
|
||||
@@ -1,59 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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 "wayland.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
bool waylandIdleInit(void)
|
||||
{
|
||||
if (!wlWm.idleInhibitManager)
|
||||
DEBUG_WARN("zwp_idle_inhibit_manager_v1 not exported by compositor, will "
|
||||
"not be able to suppress idle states");
|
||||
return true;
|
||||
}
|
||||
|
||||
void waylandIdleFree(void)
|
||||
{
|
||||
if (wlWm.idleInhibitManager)
|
||||
{
|
||||
waylandUninhibitIdle();
|
||||
zwp_idle_inhibit_manager_v1_destroy(wlWm.idleInhibitManager);
|
||||
}
|
||||
}
|
||||
|
||||
void waylandInhibitIdle(void)
|
||||
{
|
||||
if (wlWm.idleInhibitManager && !wlWm.idleInhibitor)
|
||||
wlWm.idleInhibitor = zwp_idle_inhibit_manager_v1_create_inhibitor(
|
||||
wlWm.idleInhibitManager, wlWm.surface);
|
||||
}
|
||||
|
||||
void waylandUninhibitIdle(void)
|
||||
{
|
||||
if (wlWm.idleInhibitor)
|
||||
{
|
||||
zwp_idle_inhibitor_v1_destroy(wlWm.idleInhibitor);
|
||||
wlWm.idleInhibitor = NULL;
|
||||
}
|
||||
}
|
||||
@@ -1,494 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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 "wayland.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "app.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
// Mouse-handling listeners.
|
||||
|
||||
static void pointerMotionHandler(void * data, struct wl_pointer * pointer,
|
||||
uint32_t serial, wl_fixed_t sxW, wl_fixed_t syW)
|
||||
{
|
||||
wlWm.cursorX = wl_fixed_to_double(sxW);
|
||||
wlWm.cursorY = wl_fixed_to_double(syW);
|
||||
app_updateCursorPos(wlWm.cursorX, wlWm.cursorY);
|
||||
|
||||
if (!wlWm.warpSupport && !wlWm.relativePointer)
|
||||
app_handleMouseBasic();
|
||||
}
|
||||
|
||||
static void pointerEnterHandler(void * data, struct wl_pointer * pointer,
|
||||
uint32_t serial, struct wl_surface * surface, wl_fixed_t sxW,
|
||||
wl_fixed_t syW)
|
||||
{
|
||||
if (surface != wlWm.surface)
|
||||
return;
|
||||
|
||||
wlWm.pointerInSurface = true;
|
||||
app_handleEnterEvent(true);
|
||||
|
||||
wl_pointer_set_cursor(pointer, serial, wlWm.showPointer ? wlWm.cursor : NULL, 0, 0);
|
||||
wlWm.pointerEnterSerial = serial;
|
||||
|
||||
wlWm.cursorX = wl_fixed_to_double(sxW);
|
||||
wlWm.cursorY = wl_fixed_to_double(syW);
|
||||
app_updateCursorPos(wlWm.cursorX, wlWm.cursorY);
|
||||
|
||||
if (wlWm.warpSupport)
|
||||
{
|
||||
app_handleMouseRelative(0.0, 0.0, 0.0, 0.0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (wlWm.relativePointer)
|
||||
return;
|
||||
|
||||
app_resyncMouseBasic();
|
||||
app_handleMouseBasic();
|
||||
}
|
||||
|
||||
static void pointerLeaveHandler(void * data, struct wl_pointer * pointer,
|
||||
uint32_t serial, struct wl_surface * surface)
|
||||
{
|
||||
if (surface != wlWm.surface)
|
||||
return;
|
||||
|
||||
wlWm.pointerInSurface = false;
|
||||
app_handleEnterEvent(false);
|
||||
}
|
||||
|
||||
static void pointerAxisHandler(void * data, struct wl_pointer * pointer,
|
||||
uint32_t serial, uint32_t axis, wl_fixed_t value)
|
||||
{
|
||||
int button = value > 0 ?
|
||||
5 /* SPICE_MOUSE_BUTTON_DOWN */ :
|
||||
4 /* SPICE_MOUSE_BUTTON_UP */;
|
||||
app_handleButtonPress(button);
|
||||
app_handleButtonRelease(button);
|
||||
}
|
||||
|
||||
static int mapWaylandToSpiceButton(uint32_t button)
|
||||
{
|
||||
switch (button)
|
||||
{
|
||||
case BTN_LEFT:
|
||||
return 1; // SPICE_MOUSE_BUTTON_LEFT
|
||||
case BTN_MIDDLE:
|
||||
return 2; // SPICE_MOUSE_BUTTON_MIDDLE
|
||||
case BTN_RIGHT:
|
||||
return 3; // SPICE_MOUSE_BUTTON_RIGHT
|
||||
case BTN_SIDE:
|
||||
return 6; // SPICE_MOUSE_BUTTON_SIDE
|
||||
case BTN_EXTRA:
|
||||
return 7; // SPICE_MOUSE_BUTTON_EXTRA
|
||||
}
|
||||
|
||||
return 0; // SPICE_MOUSE_BUTTON_INVALID
|
||||
}
|
||||
|
||||
static void pointerButtonHandler(void *data, struct wl_pointer *pointer,
|
||||
uint32_t serial, uint32_t time, uint32_t button, uint32_t stateW)
|
||||
{
|
||||
button = mapWaylandToSpiceButton(button);
|
||||
|
||||
if (stateW == WL_POINTER_BUTTON_STATE_PRESSED)
|
||||
app_handleButtonPress(button);
|
||||
else
|
||||
app_handleButtonRelease(button);
|
||||
}
|
||||
|
||||
static const struct wl_pointer_listener pointerListener = {
|
||||
.enter = pointerEnterHandler,
|
||||
.leave = pointerLeaveHandler,
|
||||
.motion = pointerMotionHandler,
|
||||
.button = pointerButtonHandler,
|
||||
.axis = pointerAxisHandler,
|
||||
};
|
||||
|
||||
static void relativePointerMotionHandler(void * data,
|
||||
struct zwp_relative_pointer_v1 *pointer, uint32_t timeHi, uint32_t timeLo,
|
||||
wl_fixed_t dxW, wl_fixed_t dyW, wl_fixed_t dxUnaccelW,
|
||||
wl_fixed_t dyUnaccelW)
|
||||
{
|
||||
wlWm.cursorX += wl_fixed_to_double(dxW);
|
||||
wlWm.cursorY += wl_fixed_to_double(dyW);
|
||||
app_updateCursorPos(wlWm.cursorX, wlWm.cursorY);
|
||||
|
||||
app_handleMouseRelative(
|
||||
wl_fixed_to_double(dxW),
|
||||
wl_fixed_to_double(dyW),
|
||||
wl_fixed_to_double(dxUnaccelW),
|
||||
wl_fixed_to_double(dyUnaccelW));
|
||||
}
|
||||
|
||||
static const struct zwp_relative_pointer_v1_listener relativePointerListener = {
|
||||
.relative_motion = relativePointerMotionHandler,
|
||||
};
|
||||
|
||||
// Keyboard-handling listeners.
|
||||
|
||||
static void keyboardKeymapHandler(void * data, struct wl_keyboard * keyboard,
|
||||
uint32_t format, int fd, uint32_t size)
|
||||
{
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void keyboardEnterHandler(void * data, struct wl_keyboard * keyboard,
|
||||
uint32_t serial, struct wl_surface * surface, struct wl_array * keys)
|
||||
{
|
||||
if (surface != wlWm.surface)
|
||||
return;
|
||||
|
||||
wlWm.focusedOnSurface = true;
|
||||
app_handleFocusEvent(true);
|
||||
wlWm.keyboardEnterSerial = serial;
|
||||
|
||||
uint32_t * key;
|
||||
wl_array_for_each(key, keys)
|
||||
app_handleKeyPress(*key);
|
||||
}
|
||||
|
||||
static void keyboardLeaveHandler(void * data, struct wl_keyboard * keyboard,
|
||||
uint32_t serial, struct wl_surface * surface)
|
||||
{
|
||||
if (surface != wlWm.surface)
|
||||
return;
|
||||
|
||||
wlWm.focusedOnSurface = false;
|
||||
waylandCBInvalidate();
|
||||
app_handleFocusEvent(false);
|
||||
}
|
||||
|
||||
static void keyboardKeyHandler(void * data, struct wl_keyboard * keyboard,
|
||||
uint32_t serial, uint32_t time, uint32_t key, uint32_t state)
|
||||
{
|
||||
if (!wlWm.focusedOnSurface)
|
||||
return;
|
||||
|
||||
if (state == WL_KEYBOARD_KEY_STATE_PRESSED)
|
||||
app_handleKeyPress(key);
|
||||
else
|
||||
app_handleKeyRelease(key);
|
||||
}
|
||||
|
||||
static void keyboardModifiersHandler(void * data,
|
||||
struct wl_keyboard * keyboard, uint32_t serial, uint32_t modsDepressed,
|
||||
uint32_t modsLatched, uint32_t modsLocked, uint32_t group)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
static const struct wl_keyboard_listener keyboardListener = {
|
||||
.keymap = keyboardKeymapHandler,
|
||||
.enter = keyboardEnterHandler,
|
||||
.leave = keyboardLeaveHandler,
|
||||
.key = keyboardKeyHandler,
|
||||
.modifiers = keyboardModifiersHandler,
|
||||
};
|
||||
|
||||
// Seat-handling listeners.
|
||||
|
||||
static void handlePointerCapability(uint32_t capabilities)
|
||||
{
|
||||
bool hasPointer = capabilities & WL_SEAT_CAPABILITY_POINTER;
|
||||
if (!hasPointer && wlWm.pointer)
|
||||
{
|
||||
wl_pointer_destroy(wlWm.pointer);
|
||||
wlWm.pointer = NULL;
|
||||
}
|
||||
else if (hasPointer && !wlWm.pointer)
|
||||
{
|
||||
wlWm.pointer = wl_seat_get_pointer(wlWm.seat);
|
||||
wl_pointer_add_listener(wlWm.pointer, &pointerListener, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void handleKeyboardCapability(uint32_t capabilities)
|
||||
{
|
||||
bool hasKeyboard = capabilities & WL_SEAT_CAPABILITY_KEYBOARD;
|
||||
if (!hasKeyboard && wlWm.keyboard)
|
||||
{
|
||||
wl_keyboard_destroy(wlWm.keyboard);
|
||||
wlWm.keyboard = NULL;
|
||||
}
|
||||
else if (hasKeyboard && !wlWm.keyboard)
|
||||
{
|
||||
wlWm.keyboard = wl_seat_get_keyboard(wlWm.seat);
|
||||
wl_keyboard_add_listener(wlWm.keyboard, &keyboardListener, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void seatCapabilitiesHandler(void * data, struct wl_seat * seat,
|
||||
uint32_t capabilities)
|
||||
{
|
||||
wlWm.capabilities = capabilities;
|
||||
handlePointerCapability(capabilities);
|
||||
handleKeyboardCapability(capabilities);
|
||||
}
|
||||
|
||||
static void seatNameHandler(void * data, struct wl_seat * seat,
|
||||
const char * name)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
static const struct wl_seat_listener seatListener = {
|
||||
.capabilities = seatCapabilitiesHandler,
|
||||
.name = seatNameHandler,
|
||||
};
|
||||
|
||||
bool waylandInputInit(void)
|
||||
{
|
||||
if (!wlWm.seat)
|
||||
{
|
||||
DEBUG_ERROR("Compositor missing wl_seat, will not proceed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (wlWm.warpSupport && (!wlWm.relativePointerManager || !wlWm.pointerConstraints))
|
||||
{
|
||||
DEBUG_WARN("Cursor warp is requested, but cannot be honoured due to lack "
|
||||
"of zwp_relative_pointer_manager_v1 or zwp_pointer_constraints_v1");
|
||||
wlWm.warpSupport = false;
|
||||
}
|
||||
|
||||
if (!wlWm.relativePointerManager)
|
||||
DEBUG_WARN("zwp_relative_pointer_manager_v1 not exported by compositor, "
|
||||
"mouse will not be captured");
|
||||
|
||||
if (!wlWm.pointerConstraints)
|
||||
DEBUG_WARN("zwp_pointer_constraints_v1 not exported by compositor, mouse "
|
||||
"will not be captured");
|
||||
|
||||
if (!wlWm.keyboardInhibitManager)
|
||||
DEBUG_WARN("zwp_keyboard_shortcuts_inhibit_manager_v1 not exported by "
|
||||
"compositor, keyboard will not be grabbed");
|
||||
|
||||
wl_seat_add_listener(wlWm.seat, &seatListener, NULL);
|
||||
wl_display_roundtrip(wlWm.display);
|
||||
|
||||
if (wlWm.warpSupport)
|
||||
{
|
||||
wlWm.relativePointer =
|
||||
zwp_relative_pointer_manager_v1_get_relative_pointer(
|
||||
wlWm.relativePointerManager, wlWm.pointer);
|
||||
zwp_relative_pointer_v1_add_listener(wlWm.relativePointer,
|
||||
&relativePointerListener, NULL);
|
||||
}
|
||||
|
||||
LG_LOCK_INIT(wlWm.confineLock);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void waylandInputFree(void)
|
||||
{
|
||||
waylandUngrabPointer();
|
||||
LG_LOCK_FREE(wlWm.confineLock);
|
||||
wl_pointer_destroy(wlWm.pointer);
|
||||
wl_keyboard_destroy(wlWm.keyboard);
|
||||
wl_seat_destroy(wlWm.seat);
|
||||
}
|
||||
|
||||
void waylandGrabPointer(void)
|
||||
{
|
||||
if (!wlWm.relativePointerManager || !wlWm.pointerConstraints)
|
||||
return;
|
||||
|
||||
if (!wlWm.warpSupport && !wlWm.relativePointer)
|
||||
{
|
||||
wlWm.relativePointer =
|
||||
zwp_relative_pointer_manager_v1_get_relative_pointer(
|
||||
wlWm.relativePointerManager, wlWm.pointer);
|
||||
zwp_relative_pointer_v1_add_listener(wlWm.relativePointer,
|
||||
&relativePointerListener, NULL);
|
||||
}
|
||||
|
||||
INTERLOCKED_SECTION(wlWm.confineLock,
|
||||
{
|
||||
if (!wlWm.confinedPointer)
|
||||
{
|
||||
wlWm.confinedPointer = zwp_pointer_constraints_v1_confine_pointer(
|
||||
wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, NULL,
|
||||
ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void waylandUngrabPointer(void)
|
||||
{
|
||||
INTERLOCKED_SECTION(wlWm.confineLock,
|
||||
{
|
||||
if (wlWm.confinedPointer)
|
||||
{
|
||||
zwp_confined_pointer_v1_destroy(wlWm.confinedPointer);
|
||||
wlWm.confinedPointer = NULL;
|
||||
}
|
||||
});
|
||||
|
||||
if (!wlWm.warpSupport)
|
||||
{
|
||||
if (!wlWm.relativePointer)
|
||||
{
|
||||
wlWm.relativePointer =
|
||||
zwp_relative_pointer_manager_v1_get_relative_pointer(
|
||||
wlWm.relativePointerManager, wlWm.pointer);
|
||||
zwp_relative_pointer_v1_add_listener(wlWm.relativePointer,
|
||||
&relativePointerListener, NULL);
|
||||
}
|
||||
|
||||
app_resyncMouseBasic();
|
||||
app_handleMouseBasic();
|
||||
}
|
||||
}
|
||||
|
||||
void waylandCapturePointer(void)
|
||||
{
|
||||
if (!wlWm.warpSupport)
|
||||
{
|
||||
waylandGrabPointer();
|
||||
return;
|
||||
}
|
||||
|
||||
INTERLOCKED_SECTION(wlWm.confineLock,
|
||||
{
|
||||
if (wlWm.confinedPointer)
|
||||
{
|
||||
zwp_confined_pointer_v1_destroy(wlWm.confinedPointer);
|
||||
wlWm.confinedPointer = NULL;
|
||||
}
|
||||
|
||||
wlWm.lockedPointer = zwp_pointer_constraints_v1_lock_pointer(
|
||||
wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, NULL,
|
||||
ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
|
||||
});
|
||||
}
|
||||
|
||||
void waylandUncapturePointer(void)
|
||||
{
|
||||
INTERLOCKED_SECTION(wlWm.confineLock,
|
||||
{
|
||||
if (wlWm.lockedPointer)
|
||||
{
|
||||
zwp_locked_pointer_v1_destroy(wlWm.lockedPointer);
|
||||
wlWm.lockedPointer = NULL;
|
||||
}
|
||||
|
||||
/* we need to ungrab the pointer on the following conditions when exiting capture mode:
|
||||
* - if warp is not supported, exit via window edge detection will never work
|
||||
* as the cursor can not be warped out of the window when we release it.
|
||||
* - if the format is invalid as we do not know where the guest cursor is,
|
||||
* which also breaks edge detection.
|
||||
* - if the user has opted to use captureInputOnly mode.
|
||||
*/
|
||||
if (!wlWm.warpSupport || !app_isFormatValid() || app_isCaptureOnlyMode())
|
||||
{
|
||||
waylandUngrabPointer();
|
||||
}
|
||||
else
|
||||
{
|
||||
wlWm.confinedPointer = zwp_pointer_constraints_v1_confine_pointer(
|
||||
wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, NULL,
|
||||
ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void waylandGrabKeyboard(void)
|
||||
{
|
||||
if (wlWm.keyboardInhibitManager && !wlWm.keyboardInhibitor)
|
||||
{
|
||||
wlWm.keyboardInhibitor = zwp_keyboard_shortcuts_inhibit_manager_v1_inhibit_shortcuts(
|
||||
wlWm.keyboardInhibitManager, wlWm.surface, wlWm.seat);
|
||||
}
|
||||
}
|
||||
|
||||
void waylandUngrabKeyboard(void)
|
||||
{
|
||||
if (wlWm.keyboardInhibitor)
|
||||
{
|
||||
zwp_keyboard_shortcuts_inhibitor_v1_destroy(wlWm.keyboardInhibitor);
|
||||
wlWm.keyboardInhibitor = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void waylandWarpPointer(int x, int y, bool exiting)
|
||||
{
|
||||
if (!wlWm.pointerInSurface || wlWm.lockedPointer)
|
||||
return;
|
||||
|
||||
INTERLOCKED_SECTION(wlWm.confineLock,
|
||||
{
|
||||
if (wlWm.lockedPointer)
|
||||
{
|
||||
LG_UNLOCK(wlWm.confineLock);
|
||||
return;
|
||||
}
|
||||
|
||||
if (x < 0) x = 0;
|
||||
else if (x >= wlWm.width) x = wlWm.width - 1;
|
||||
if (y < 0) y = 0;
|
||||
else if (y >= wlWm.height) y = wlWm.height - 1;
|
||||
|
||||
struct wl_region * region = wl_compositor_create_region(wlWm.compositor);
|
||||
wl_region_add(region, x, y, 1, 1);
|
||||
|
||||
if (wlWm.confinedPointer)
|
||||
{
|
||||
zwp_confined_pointer_v1_set_region(wlWm.confinedPointer, region);
|
||||
wl_surface_commit(wlWm.surface);
|
||||
zwp_confined_pointer_v1_set_region(wlWm.confinedPointer, NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
struct zwp_confined_pointer_v1 * confine;
|
||||
confine = zwp_pointer_constraints_v1_confine_pointer(
|
||||
wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, region,
|
||||
ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
|
||||
wl_surface_commit(wlWm.surface);
|
||||
zwp_confined_pointer_v1_destroy(confine);
|
||||
}
|
||||
|
||||
wl_surface_commit(wlWm.surface);
|
||||
wl_region_destroy(region);
|
||||
});
|
||||
}
|
||||
|
||||
void waylandRealignPointer(void)
|
||||
{
|
||||
if (!wlWm.warpSupport)
|
||||
app_resyncMouseBasic();
|
||||
}
|
||||
|
||||
void waylandGuestPointerUpdated(double x, double y, double localX, double localY)
|
||||
{
|
||||
if (!wlWm.warpSupport || !wlWm.pointerInSurface || wlWm.lockedPointer)
|
||||
return;
|
||||
|
||||
waylandWarpPointer((int) localX, (int) localY, false);
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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 "wayland.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
static void outputGeometryHandler(void * data, struct wl_output * output, int32_t x, int32_t y,
|
||||
int32_t physical_width, int32_t physical_height, int32_t subpixel, const char * make,
|
||||
const char * model, int32_t output_transform)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
static void outputModeHandler(void * data, struct wl_output * wl_output, uint32_t flags,
|
||||
int32_t width, int32_t height, int32_t refresh)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
static void outputDoneHandler(void * data, struct wl_output * output)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
static void outputScaleHandler(void * opaque, struct wl_output * output, int32_t scale)
|
||||
{
|
||||
struct WaylandOutput * node = opaque;
|
||||
node->scale = scale;
|
||||
waylandWindowUpdateScale();
|
||||
}
|
||||
|
||||
static const struct wl_output_listener outputListener = {
|
||||
.geometry = outputGeometryHandler,
|
||||
.mode = outputModeHandler,
|
||||
.done = outputDoneHandler,
|
||||
.scale = outputScaleHandler,
|
||||
};
|
||||
|
||||
bool waylandOutputInit(void)
|
||||
{
|
||||
wl_list_init(&wlWm.outputs);
|
||||
return true;
|
||||
}
|
||||
|
||||
void waylandOutputFree(void)
|
||||
{
|
||||
struct WaylandOutput * node;
|
||||
struct WaylandOutput * temp;
|
||||
wl_list_for_each_safe(node, temp, &wlWm.outputs, link)
|
||||
{
|
||||
if (node->version >= 3)
|
||||
wl_output_release(node->output);
|
||||
wl_list_remove(&node->link);
|
||||
free(node);
|
||||
}
|
||||
}
|
||||
|
||||
void waylandOutputBind(uint32_t name, uint32_t version)
|
||||
{
|
||||
struct WaylandOutput * node = malloc(sizeof(struct WaylandOutput));
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
if (version < 2)
|
||||
{
|
||||
DEBUG_WARN("wl_output version too old: expected >= 2, got %d", version);
|
||||
return;
|
||||
}
|
||||
|
||||
node->name = name;
|
||||
node->scale = 0;
|
||||
node->version = version;
|
||||
node->output = wl_registry_bind(wlWm.registry, name,
|
||||
&wl_output_interface, version >= 3 ? 3 : 2);
|
||||
|
||||
if (!node->output)
|
||||
{
|
||||
DEBUG_ERROR("Failed to bind to wl_output %u\n", name);
|
||||
free(node);
|
||||
return;
|
||||
}
|
||||
|
||||
wl_output_add_listener(node->output, &outputListener, node);
|
||||
wl_list_insert(&wlWm.outputs, &node->link);
|
||||
}
|
||||
|
||||
void waylandOutputTryUnbind(uint32_t name)
|
||||
{
|
||||
struct WaylandOutput * node;
|
||||
|
||||
wl_list_for_each(node, &wlWm.outputs, link)
|
||||
{
|
||||
if (node->name == name)
|
||||
{
|
||||
if (node->version >= 3)
|
||||
wl_output_release(node->output);
|
||||
wl_list_remove(&node->link);
|
||||
free(node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32_t waylandOutputGetScale(struct wl_output * output)
|
||||
{
|
||||
struct WaylandOutput * node;
|
||||
|
||||
wl_list_for_each(node, &wlWm.outputs, link)
|
||||
if (node->output == output)
|
||||
return node->scale;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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 "wayland.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/locking.h"
|
||||
|
||||
#define EPOLL_EVENTS 10 // Maximum number of fds we can process at once in waylandWait
|
||||
|
||||
static void waylandDisplayCallback(uint32_t events, void * opaque)
|
||||
{
|
||||
if (events & EPOLLERR)
|
||||
wl_display_cancel_read(wlWm.display);
|
||||
else
|
||||
wl_display_read_events(wlWm.display);
|
||||
wl_display_dispatch_pending(wlWm.display);
|
||||
}
|
||||
|
||||
bool waylandPollInit(void)
|
||||
{
|
||||
wlWm.epollFd = epoll_create1(EPOLL_CLOEXEC);
|
||||
if (wlWm.epollFd < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize epoll: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
wl_list_init(&wlWm.poll);
|
||||
wl_list_init(&wlWm.pollFree);
|
||||
LG_LOCK_INIT(wlWm.pollLock);
|
||||
LG_LOCK_INIT(wlWm.pollFreeLock);
|
||||
|
||||
wlWm.displayFd = wl_display_get_fd(wlWm.display);
|
||||
if (!waylandPollRegister(wlWm.displayFd, waylandDisplayCallback, NULL, EPOLLIN))
|
||||
{
|
||||
DEBUG_ERROR("Failed register display to epoll: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void waylandWait(unsigned int time)
|
||||
{
|
||||
while (wl_display_prepare_read(wlWm.display))
|
||||
wl_display_dispatch_pending(wlWm.display);
|
||||
wl_display_flush(wlWm.display);
|
||||
|
||||
struct epoll_event events[EPOLL_EVENTS];
|
||||
int count;
|
||||
if ((count = epoll_wait(wlWm.epollFd, events, EPOLL_EVENTS, time)) < 0)
|
||||
{
|
||||
if (errno != EINTR)
|
||||
DEBUG_INFO("epoll failed: %s", strerror(errno));
|
||||
wl_display_cancel_read(wlWm.display);
|
||||
return;
|
||||
}
|
||||
|
||||
bool sawDisplay = false;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
struct WaylandPoll * poll = events[i].data.ptr;
|
||||
if (!poll->removed)
|
||||
poll->callback(events[i].events, poll->opaque);
|
||||
if (poll->fd == wlWm.displayFd)
|
||||
sawDisplay = true;
|
||||
}
|
||||
|
||||
if (!sawDisplay)
|
||||
wl_display_cancel_read(wlWm.display);
|
||||
|
||||
INTERLOCKED_SECTION(wlWm.pollFreeLock,
|
||||
{
|
||||
struct WaylandPoll * node;
|
||||
struct WaylandPoll * temp;
|
||||
wl_list_for_each_safe(node, temp, &wlWm.pollFree, link)
|
||||
{
|
||||
wl_list_remove(&node->link);
|
||||
free(node);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void waylandPollRemoveNode(struct WaylandPoll * node)
|
||||
{
|
||||
INTERLOCKED_SECTION(wlWm.pollLock,
|
||||
{
|
||||
wl_list_remove(&node->link);
|
||||
});
|
||||
}
|
||||
|
||||
bool waylandPollRegister(int fd, WaylandPollCallback callback, void * opaque, uint32_t events)
|
||||
{
|
||||
struct WaylandPoll * node = malloc(sizeof(struct WaylandPoll));
|
||||
if (!node)
|
||||
return false;
|
||||
|
||||
node->fd = fd;
|
||||
node->removed = false;
|
||||
node->callback = callback;
|
||||
node->opaque = opaque;
|
||||
|
||||
INTERLOCKED_SECTION(wlWm.pollLock,
|
||||
{
|
||||
wl_list_insert(&wlWm.poll, &node->link);
|
||||
});
|
||||
|
||||
if (epoll_ctl(wlWm.epollFd, EPOLL_CTL_ADD, fd, &(struct epoll_event) {
|
||||
.events = events,
|
||||
.data = (epoll_data_t) { .ptr = node },
|
||||
}) < 0)
|
||||
{
|
||||
waylandPollRemoveNode(node);
|
||||
free(node);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool waylandPollUnregister(int fd)
|
||||
{
|
||||
struct WaylandPoll * node = NULL;
|
||||
INTERLOCKED_SECTION(wlWm.pollLock,
|
||||
{
|
||||
wl_list_for_each(node, &wlWm.poll, link)
|
||||
{
|
||||
if (node->fd == fd)
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
if (!node)
|
||||
{
|
||||
DEBUG_ERROR("Attempt to unregister a fd that was not registered: %d", fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
node->removed = true;
|
||||
if (epoll_ctl(wlWm.epollFd, EPOLL_CTL_DEL, fd, NULL) < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to unregistered from epoll: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
waylandPollRemoveNode(node);
|
||||
|
||||
INTERLOCKED_SECTION(wlWm.pollFreeLock,
|
||||
{
|
||||
wl_list_insert(&wlWm.pollFree, &node->link);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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 "wayland.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
static void registryGlobalHandler(void * data, struct wl_registry * registry,
|
||||
uint32_t name, const char * interface, uint32_t version)
|
||||
{
|
||||
if (!strcmp(interface, wl_output_interface.name))
|
||||
waylandOutputBind(name, version);
|
||||
else if (!strcmp(interface, wl_seat_interface.name) && !wlWm.seat)
|
||||
wlWm.seat = wl_registry_bind(wlWm.registry, name, &wl_seat_interface, 1);
|
||||
else if (!strcmp(interface, wl_shm_interface.name))
|
||||
wlWm.shm = wl_registry_bind(wlWm.registry, name, &wl_shm_interface, 1);
|
||||
else if (!strcmp(interface, wl_compositor_interface.name) && version >= 3)
|
||||
wlWm.compositor = wl_registry_bind(wlWm.registry, name,
|
||||
// we only need v3 to run, but v4 can use eglSwapBuffersWithDamageKHR
|
||||
&wl_compositor_interface, version > 4 ? 4 : version);
|
||||
#ifndef ENABLE_LIBDECOR
|
||||
else if (!strcmp(interface, xdg_wm_base_interface.name))
|
||||
wlWm.xdgWmBase = wl_registry_bind(wlWm.registry, name, &xdg_wm_base_interface, 1);
|
||||
else if (!strcmp(interface, zxdg_decoration_manager_v1_interface.name))
|
||||
wlWm.xdgDecorationManager = wl_registry_bind(wlWm.registry, name,
|
||||
&zxdg_decoration_manager_v1_interface, 1);
|
||||
#endif
|
||||
else if (!strcmp(interface, zwp_relative_pointer_manager_v1_interface.name))
|
||||
wlWm.relativePointerManager = wl_registry_bind(wlWm.registry, name,
|
||||
&zwp_relative_pointer_manager_v1_interface, 1);
|
||||
else if (!strcmp(interface, zwp_pointer_constraints_v1_interface.name))
|
||||
wlWm.pointerConstraints = wl_registry_bind(wlWm.registry, name,
|
||||
&zwp_pointer_constraints_v1_interface, 1);
|
||||
else if (!strcmp(interface, zwp_keyboard_shortcuts_inhibit_manager_v1_interface.name))
|
||||
wlWm.keyboardInhibitManager = wl_registry_bind(wlWm.registry, name,
|
||||
&zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1);
|
||||
else if (!strcmp(interface, wl_data_device_manager_interface.name) && version >= 3)
|
||||
wlWm.dataDeviceManager = wl_registry_bind(wlWm.registry, name,
|
||||
&wl_data_device_manager_interface, 3);
|
||||
else if (!strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name))
|
||||
wlWm.idleInhibitManager = wl_registry_bind(wlWm.registry, name,
|
||||
&zwp_idle_inhibit_manager_v1_interface, 1);
|
||||
}
|
||||
|
||||
static void registryGlobalRemoveHandler(void * data,
|
||||
struct wl_registry * registry, uint32_t name)
|
||||
{
|
||||
waylandOutputTryUnbind(name);
|
||||
}
|
||||
|
||||
static const struct wl_registry_listener registryListener = {
|
||||
.global = registryGlobalHandler,
|
||||
.global_remove = registryGlobalRemoveHandler,
|
||||
};
|
||||
|
||||
bool waylandRegistryInit(void)
|
||||
{
|
||||
wlWm.registry = wl_display_get_registry(wlWm.display);
|
||||
if (!wlWm.registry)
|
||||
{
|
||||
DEBUG_ERROR("Unable to find wl_registry");
|
||||
return false;
|
||||
}
|
||||
|
||||
wl_registry_add_listener(wlWm.registry, ®istryListener, NULL);
|
||||
wl_display_roundtrip(wlWm.display);
|
||||
return true;
|
||||
}
|
||||
|
||||
void waylandRegistryFree(void)
|
||||
{
|
||||
wl_registry_destroy(wlWm.registry);
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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 "wayland.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <libdecor.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "app.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
struct libdecor_configuration {
|
||||
uint32_t serial;
|
||||
|
||||
bool has_window_state;
|
||||
enum libdecor_window_state window_state;
|
||||
|
||||
bool has_size;
|
||||
int window_width;
|
||||
int window_height;
|
||||
};
|
||||
|
||||
static void libdecorHandleError(struct libdecor * context, enum libdecor_error error,
|
||||
const char *message)
|
||||
{
|
||||
DEBUG_ERROR("Got libdecor error (%d): %s", error, message);
|
||||
}
|
||||
|
||||
static void libdecorFrameConfigure(struct libdecor_frame * frame,
|
||||
struct libdecor_configuration * configuration, void * opaque)
|
||||
{
|
||||
int width, height;
|
||||
if (libdecor_configuration_get_content_size(configuration, frame, &width, &height)) {
|
||||
wlWm.width = width;
|
||||
wlWm.height = height;
|
||||
}
|
||||
|
||||
enum libdecor_window_state windowState;
|
||||
if (libdecor_configuration_get_window_state(configuration, &windowState))
|
||||
wlWm.fullscreen = windowState & LIBDECOR_WINDOW_STATE_FULLSCREEN;
|
||||
|
||||
struct libdecor_state * state = libdecor_state_new(wlWm.width, wlWm.height);
|
||||
libdecor_frame_commit(frame, state, wlWm.configured ? NULL : configuration);
|
||||
libdecor_state_free(state);
|
||||
|
||||
if (wlWm.configured)
|
||||
{
|
||||
wlWm.needsResize = true;
|
||||
wlWm.resizeSerial = configuration->serial;
|
||||
}
|
||||
else
|
||||
wlWm.configured = true;
|
||||
}
|
||||
|
||||
static void libdecorFrameClose(struct libdecor_frame * frame, void * opaque)
|
||||
{
|
||||
app_handleCloseEvent();
|
||||
}
|
||||
|
||||
static void libdecorFrameCommit(struct libdecor_frame * frame, void * opaque)
|
||||
{
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
|
||||
static struct libdecor_interface libdecorListener = {
|
||||
libdecorHandleError,
|
||||
};
|
||||
|
||||
static struct libdecor_frame_interface libdecorFrameListener = {
|
||||
libdecorFrameConfigure,
|
||||
libdecorFrameClose,
|
||||
libdecorFrameCommit,
|
||||
};
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
bool waylandShellInit(const char * title, bool fullscreen, bool maximize, bool borderless)
|
||||
{
|
||||
wlWm.libdecor = libdecor_new(wlWm.display, &libdecorListener);
|
||||
wlWm.libdecorFrame = libdecor_decorate(wlWm.libdecor, wlWm.surface, &libdecorFrameListener, NULL);
|
||||
|
||||
libdecor_frame_set_app_id(wlWm.libdecorFrame, "looking-glass-client");
|
||||
libdecor_frame_set_title(wlWm.libdecorFrame, title);
|
||||
libdecor_frame_map(wlWm.libdecorFrame);
|
||||
|
||||
while (!wlWm.configured)
|
||||
wl_display_roundtrip(wlWm.display);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void waylandShellAckConfigureIfNeeded(void)
|
||||
{
|
||||
if (wlWm.resizeSerial)
|
||||
{
|
||||
xdg_surface_ack_configure(libdecor_frame_get_xdg_surface(wlWm.libdecorFrame), wlWm.resizeSerial);
|
||||
wlWm.resizeSerial = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void waylandSetFullscreen(bool fs)
|
||||
{
|
||||
if (fs)
|
||||
libdecor_frame_set_fullscreen(wlWm.libdecorFrame, NULL);
|
||||
else
|
||||
libdecor_frame_unset_fullscreen(wlWm.libdecorFrame);
|
||||
}
|
||||
|
||||
bool waylandGetFullscreen(void)
|
||||
{
|
||||
return wlWm.fullscreen;
|
||||
}
|
||||
|
||||
void waylandMinimize(void)
|
||||
{
|
||||
libdecor_frame_set_minimized(wlWm.libdecorFrame);
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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 "wayland.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "app.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
// XDG WM base listeners.
|
||||
|
||||
static void xdgWmBasePing(void * data, struct xdg_wm_base * xdgWmBase, uint32_t serial)
|
||||
{
|
||||
xdg_wm_base_pong(xdgWmBase, serial);
|
||||
}
|
||||
|
||||
static const struct xdg_wm_base_listener xdgWmBaseListener = {
|
||||
.ping = xdgWmBasePing,
|
||||
};
|
||||
|
||||
// XDG Surface listeners.
|
||||
|
||||
static void xdgSurfaceConfigure(void * data, struct xdg_surface * xdgSurface,
|
||||
uint32_t serial)
|
||||
{
|
||||
if (wlWm.configured)
|
||||
{
|
||||
wlWm.needsResize = true;
|
||||
wlWm.resizeSerial = serial;
|
||||
}
|
||||
else
|
||||
{
|
||||
xdg_surface_ack_configure(xdgSurface, serial);
|
||||
wlWm.configured = true;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct xdg_surface_listener xdgSurfaceListener = {
|
||||
.configure = xdgSurfaceConfigure,
|
||||
};
|
||||
|
||||
// XDG Toplevel listeners.
|
||||
|
||||
static void xdgToplevelConfigure(void * data, struct xdg_toplevel * xdgToplevel,
|
||||
int32_t width, int32_t height, struct wl_array * states)
|
||||
{
|
||||
wlWm.width = width;
|
||||
wlWm.height = height;
|
||||
wlWm.fullscreen = false;
|
||||
|
||||
enum xdg_toplevel_state * state;
|
||||
wl_array_for_each(state, states)
|
||||
{
|
||||
if (*state == XDG_TOPLEVEL_STATE_FULLSCREEN)
|
||||
wlWm.fullscreen = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void xdgToplevelClose(void * data, struct xdg_toplevel * xdgToplevel)
|
||||
{
|
||||
app_handleCloseEvent();
|
||||
}
|
||||
|
||||
static const struct xdg_toplevel_listener xdgToplevelListener = {
|
||||
.configure = xdgToplevelConfigure,
|
||||
.close = xdgToplevelClose,
|
||||
};
|
||||
|
||||
bool waylandShellInit(const char * title, bool fullscreen, bool maximize, bool borderless)
|
||||
{
|
||||
if (!wlWm.xdgWmBase)
|
||||
{
|
||||
DEBUG_ERROR("Compositor missing xdg_wm_base, will not proceed");
|
||||
return false;
|
||||
}
|
||||
|
||||
xdg_wm_base_add_listener(wlWm.xdgWmBase, &xdgWmBaseListener, NULL);
|
||||
|
||||
wlWm.xdgSurface = xdg_wm_base_get_xdg_surface(wlWm.xdgWmBase, wlWm.surface);
|
||||
xdg_surface_add_listener(wlWm.xdgSurface, &xdgSurfaceListener, NULL);
|
||||
|
||||
wlWm.xdgToplevel = xdg_surface_get_toplevel(wlWm.xdgSurface);
|
||||
xdg_toplevel_add_listener(wlWm.xdgToplevel, &xdgToplevelListener, NULL);
|
||||
xdg_toplevel_set_title(wlWm.xdgToplevel, title);
|
||||
xdg_toplevel_set_app_id(wlWm.xdgToplevel, "looking-glass-client");
|
||||
|
||||
if (fullscreen)
|
||||
xdg_toplevel_set_fullscreen(wlWm.xdgToplevel, NULL);
|
||||
|
||||
if (maximize)
|
||||
xdg_toplevel_set_maximized(wlWm.xdgToplevel);
|
||||
|
||||
if (wlWm.xdgDecorationManager)
|
||||
{
|
||||
wlWm.xdgToplevelDecoration = zxdg_decoration_manager_v1_get_toplevel_decoration(
|
||||
wlWm.xdgDecorationManager, wlWm.xdgToplevel);
|
||||
if (wlWm.xdgToplevelDecoration)
|
||||
{
|
||||
zxdg_toplevel_decoration_v1_set_mode(wlWm.xdgToplevelDecoration,
|
||||
borderless ?
|
||||
ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE :
|
||||
ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void waylandShellAckConfigureIfNeeded(void)
|
||||
{
|
||||
if (wlWm.resizeSerial)
|
||||
{
|
||||
xdg_surface_ack_configure(wlWm.xdgSurface, wlWm.resizeSerial);
|
||||
wlWm.resizeSerial = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void waylandSetFullscreen(bool fs)
|
||||
{
|
||||
if (fs)
|
||||
xdg_toplevel_set_fullscreen(wlWm.xdgToplevel, NULL);
|
||||
else
|
||||
xdg_toplevel_unset_fullscreen(wlWm.xdgToplevel);
|
||||
}
|
||||
|
||||
bool waylandGetFullscreen(void)
|
||||
{
|
||||
return wlWm.fullscreen;
|
||||
}
|
||||
|
||||
void waylandMinimize(void)
|
||||
{
|
||||
xdg_toplevel_set_minimized(wlWm.xdgToplevel);
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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 "wayland.h"
|
||||
|
||||
struct WaylandDSState wlWm;
|
||||
struct WCBState wlCb;
|
||||
@@ -1,187 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include "wayland.h"
|
||||
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/option.h"
|
||||
|
||||
static struct Option waylandOptions[] =
|
||||
{
|
||||
{
|
||||
.module = "wayland",
|
||||
.name = "warpSupport",
|
||||
.description = "Enable cursor warping",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true,
|
||||
},
|
||||
{0}
|
||||
};
|
||||
|
||||
static bool waylandEarlyInit(void)
|
||||
{
|
||||
// Request to receive EPIPE instead of SIGPIPE when one end of a pipe
|
||||
// disconnects while a write is pending. This is useful to the Wayland
|
||||
// clipboard backend, where an arbitrary application is on the other end of
|
||||
// that pipe.
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void waylandSetup(void)
|
||||
{
|
||||
option_register(waylandOptions);
|
||||
}
|
||||
|
||||
static bool waylandProbe(void)
|
||||
{
|
||||
return getenv("WAYLAND_DISPLAY") != NULL;
|
||||
}
|
||||
|
||||
static bool waylandInit(const LG_DSInitParams params)
|
||||
{
|
||||
memset(&wlWm, 0, sizeof(wlWm));
|
||||
wl_list_init(&wlWm.surfaceOutputs);
|
||||
|
||||
wlWm.warpSupport = option_get_bool("wayland", "warpSupport");
|
||||
|
||||
wlWm.display = wl_display_connect(NULL);
|
||||
wlWm.width = params.w;
|
||||
wlWm.height = params.h;
|
||||
|
||||
if (!waylandPollInit())
|
||||
return false;
|
||||
|
||||
if (!waylandOutputInit())
|
||||
return false;
|
||||
|
||||
if (!waylandRegistryInit())
|
||||
return false;
|
||||
|
||||
if (!waylandIdleInit())
|
||||
return false;
|
||||
|
||||
if (!waylandInputInit())
|
||||
return false;
|
||||
|
||||
if (!waylandWindowInit(params.title, params.fullscreen, params.maximize, params.borderless))
|
||||
return false;
|
||||
|
||||
if (!waylandEGLInit(params.w, params.h))
|
||||
return false;
|
||||
|
||||
if (!waylandCursorInit())
|
||||
return false;
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
if (params.opengl && !waylandOpenGLInit())
|
||||
return false;
|
||||
#endif
|
||||
|
||||
wlWm.width = params.w;
|
||||
wlWm.height = params.h;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void waylandStartup(void)
|
||||
{
|
||||
}
|
||||
|
||||
static void waylandShutdown(void)
|
||||
{
|
||||
}
|
||||
|
||||
static void waylandFree(void)
|
||||
{
|
||||
waylandIdleFree();
|
||||
waylandWindowFree();
|
||||
waylandInputFree();
|
||||
waylandOutputFree();
|
||||
waylandRegistryFree();
|
||||
waylandCursorFree();
|
||||
wl_display_disconnect(wlWm.display);
|
||||
}
|
||||
|
||||
static bool waylandGetProp(LG_DSProperty prop, void * ret)
|
||||
{
|
||||
if (prop == LG_DS_WARP_SUPPORT)
|
||||
{
|
||||
*(enum LG_DSWarpSupport*)ret = wlWm.warpSupport ? LG_DS_WARP_SURFACE : LG_DS_WARP_NONE;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
struct LG_DisplayServerOps LGDS_Wayland =
|
||||
{
|
||||
.setup = waylandSetup,
|
||||
.probe = waylandProbe,
|
||||
.earlyInit = waylandEarlyInit,
|
||||
.init = waylandInit,
|
||||
.startup = waylandStartup,
|
||||
.shutdown = waylandShutdown,
|
||||
.free = waylandFree,
|
||||
.getProp = waylandGetProp,
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
.getEGLDisplay = waylandGetEGLDisplay,
|
||||
.getEGLNativeWindow = waylandGetEGLNativeWindow,
|
||||
.eglSwapBuffers = waylandEGLSwapBuffers,
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
.glCreateContext = waylandGLCreateContext,
|
||||
.glDeleteContext = waylandGLDeleteContext,
|
||||
.glMakeCurrent = waylandGLMakeCurrent,
|
||||
.glSetSwapInterval = waylandGLSetSwapInterval,
|
||||
.glSwapBuffers = waylandGLSwapBuffers,
|
||||
#endif
|
||||
.guestPointerUpdated = waylandGuestPointerUpdated,
|
||||
.showPointer = waylandShowPointer,
|
||||
.grabPointer = waylandGrabPointer,
|
||||
.ungrabPointer = waylandUngrabPointer,
|
||||
.capturePointer = waylandCapturePointer,
|
||||
.uncapturePointer = waylandUncapturePointer,
|
||||
.grabKeyboard = waylandGrabKeyboard,
|
||||
.ungrabKeyboard = waylandUngrabKeyboard,
|
||||
.warpPointer = waylandWarpPointer,
|
||||
.realignPointer = waylandRealignPointer,
|
||||
.isValidPointerPos = waylandIsValidPointerPos,
|
||||
.inhibitIdle = waylandInhibitIdle,
|
||||
.uninhibitIdle = waylandUninhibitIdle,
|
||||
.wait = waylandWait,
|
||||
.setWindowSize = waylandSetWindowSize,
|
||||
.setFullscreen = waylandSetFullscreen,
|
||||
.getFullscreen = waylandGetFullscreen,
|
||||
.minimize = waylandMinimize,
|
||||
|
||||
.cbInit = waylandCBInit,
|
||||
.cbNotice = waylandCBNotice,
|
||||
.cbRelease = waylandCBRelease,
|
||||
.cbRequest = waylandCBRequest
|
||||
};
|
||||
@@ -1,274 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <wayland-client.h>
|
||||
|
||||
#if defined(ENABLE_EGL) || defined(ENABLE_OPENGL)
|
||||
# include <wayland-egl.h>
|
||||
# include <EGL/egl.h>
|
||||
# include <EGL/eglext.h>
|
||||
#endif
|
||||
|
||||
#include "egl_dynprocs.h"
|
||||
#include "common/locking.h"
|
||||
#include "common/countedbuffer.h"
|
||||
#include "interface/displayserver.h"
|
||||
|
||||
#include "wayland-xdg-shell-client-protocol.h"
|
||||
#include "wayland-xdg-decoration-unstable-v1-client-protocol.h"
|
||||
#include "wayland-keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h"
|
||||
#include "wayland-pointer-constraints-unstable-v1-client-protocol.h"
|
||||
#include "wayland-relative-pointer-unstable-v1-client-protocol.h"
|
||||
#include "wayland-idle-inhibit-unstable-v1-client-protocol.h"
|
||||
|
||||
typedef void (*WaylandPollCallback)(uint32_t events, void * opaque);
|
||||
|
||||
struct WaylandPoll
|
||||
{
|
||||
int fd;
|
||||
bool removed;
|
||||
WaylandPollCallback callback;
|
||||
void * opaque;
|
||||
struct wl_list link;
|
||||
};
|
||||
|
||||
struct WaylandOutput
|
||||
{
|
||||
uint32_t name;
|
||||
int32_t scale;
|
||||
struct wl_output * output;
|
||||
uint32_t version;
|
||||
struct wl_list link;
|
||||
};
|
||||
|
||||
struct SurfaceOutput
|
||||
{
|
||||
struct wl_output * output;
|
||||
struct wl_list link;
|
||||
};
|
||||
|
||||
enum EGLSwapWithDamageState {
|
||||
SWAP_WITH_DAMAGE_UNKNOWN,
|
||||
SWAP_WITH_DAMAGE_UNSUPPORTED,
|
||||
SWAP_WITH_DAMAGE_KHR,
|
||||
SWAP_WITH_DAMAGE_EXT,
|
||||
};
|
||||
|
||||
struct WaylandDSState
|
||||
{
|
||||
bool pointerGrabbed;
|
||||
bool keyboardGrabbed;
|
||||
bool pointerInSurface;
|
||||
bool focusedOnSurface;
|
||||
|
||||
struct wl_display * display;
|
||||
struct wl_surface * surface;
|
||||
struct wl_registry * registry;
|
||||
struct wl_seat * seat;
|
||||
struct wl_shm * shm;
|
||||
struct wl_compositor * compositor;
|
||||
|
||||
int32_t width, height, scale;
|
||||
bool needsResize;
|
||||
bool fullscreen;
|
||||
uint32_t resizeSerial;
|
||||
bool configured;
|
||||
bool warpSupport;
|
||||
double cursorX, cursorY;
|
||||
|
||||
#if defined(ENABLE_EGL) || defined(ENABLE_OPENGL)
|
||||
struct wl_egl_window * eglWindow;
|
||||
bool eglSwapWithDamageInit;
|
||||
eglSwapBuffersWithDamageKHR_t eglSwapWithDamage;
|
||||
EGLint * eglDamageRects;
|
||||
int eglDamageRectCount;
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
EGLDisplay glDisplay;
|
||||
EGLConfig glConfig;
|
||||
EGLSurface glSurface;
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_LIBDECOR
|
||||
struct libdecor * libdecor;
|
||||
struct libdecor_frame * libdecorFrame;
|
||||
#else
|
||||
struct xdg_wm_base * xdgWmBase;
|
||||
struct xdg_surface * xdgSurface;
|
||||
struct xdg_toplevel * xdgToplevel;
|
||||
struct zxdg_decoration_manager_v1 * xdgDecorationManager;
|
||||
struct zxdg_toplevel_decoration_v1 * xdgToplevelDecoration;
|
||||
#endif
|
||||
|
||||
struct wl_surface * cursor;
|
||||
struct wl_buffer * cursorBuffer;
|
||||
|
||||
struct wl_data_device_manager * dataDeviceManager;
|
||||
|
||||
uint32_t capabilities;
|
||||
|
||||
struct wl_keyboard * keyboard;
|
||||
struct zwp_keyboard_shortcuts_inhibit_manager_v1 * keyboardInhibitManager;
|
||||
struct zwp_keyboard_shortcuts_inhibitor_v1 * keyboardInhibitor;
|
||||
uint32_t keyboardEnterSerial;
|
||||
|
||||
struct wl_pointer * pointer;
|
||||
struct zwp_relative_pointer_manager_v1 * relativePointerManager;
|
||||
struct zwp_pointer_constraints_v1 * pointerConstraints;
|
||||
struct zwp_relative_pointer_v1 * relativePointer;
|
||||
struct zwp_confined_pointer_v1 * confinedPointer;
|
||||
struct zwp_locked_pointer_v1 * lockedPointer;
|
||||
bool showPointer;
|
||||
uint32_t pointerEnterSerial;
|
||||
LG_Lock confineLock;
|
||||
|
||||
struct zwp_idle_inhibit_manager_v1 * idleInhibitManager;
|
||||
struct zwp_idle_inhibitor_v1 * idleInhibitor;
|
||||
|
||||
struct wl_list outputs; // WaylandOutput::link
|
||||
struct wl_list surfaceOutputs; // SurfaceOutput::link
|
||||
|
||||
struct wl_list poll; // WaylandPoll::link
|
||||
struct wl_list pollFree; // WaylandPoll::link
|
||||
LG_Lock pollLock;
|
||||
LG_Lock pollFreeLock;
|
||||
int epollFd;
|
||||
int displayFd;
|
||||
};
|
||||
|
||||
struct WCBTransfer
|
||||
{
|
||||
struct CountedBuffer * data;
|
||||
const char ** mimetypes;
|
||||
};
|
||||
|
||||
struct ClipboardRead
|
||||
{
|
||||
int fd;
|
||||
size_t size;
|
||||
size_t numRead;
|
||||
uint8_t * buf;
|
||||
enum LG_ClipboardData type;
|
||||
struct wl_data_offer * offer;
|
||||
};
|
||||
|
||||
struct WCBState
|
||||
{
|
||||
struct wl_data_device * dataDevice;
|
||||
char lgMimetype[64];
|
||||
|
||||
char * mimetypes[LG_CLIPBOARD_DATA_NONE];
|
||||
struct wl_data_offer * offer;
|
||||
struct wl_data_offer * dndOffer;
|
||||
|
||||
bool haveRequest;
|
||||
LG_ClipboardData type;
|
||||
|
||||
struct ClipboardRead * currentRead;
|
||||
};
|
||||
|
||||
extern struct WaylandDSState wlWm;
|
||||
extern struct WCBState wlCb;
|
||||
|
||||
// clipboard module
|
||||
bool waylandCBInit(void);
|
||||
void waylandCBRequest(LG_ClipboardData type);
|
||||
void waylandCBNotice(LG_ClipboardData type);
|
||||
void waylandCBRelease(void);
|
||||
void waylandCBInvalidate(void);
|
||||
|
||||
// cursor module
|
||||
bool waylandCursorInit(void);
|
||||
void waylandCursorFree(void);
|
||||
void waylandShowPointer(bool show);
|
||||
|
||||
// gl module
|
||||
#if defined(ENABLE_EGL) || defined(ENABLE_OPENGL)
|
||||
bool waylandEGLInit(int w, int h);
|
||||
EGLDisplay waylandGetEGLDisplay(void);
|
||||
void waylandEGLSwapBuffers(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
EGLNativeWindowType waylandGetEGLNativeWindow(void);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
bool waylandOpenGLInit(void);
|
||||
LG_DSGLContext waylandGLCreateContext(void);
|
||||
void waylandGLDeleteContext(LG_DSGLContext context);
|
||||
void waylandGLMakeCurrent(LG_DSGLContext context);
|
||||
void waylandGLSetSwapInterval(int interval);
|
||||
void waylandGLSwapBuffers(void);
|
||||
#endif
|
||||
|
||||
// idle module
|
||||
bool waylandIdleInit(void);
|
||||
void waylandIdleFree(void);
|
||||
void waylandInhibitIdle(void);
|
||||
void waylandUninhibitIdle(void);
|
||||
|
||||
// input module
|
||||
bool waylandInputInit(void);
|
||||
void waylandInputFree(void);
|
||||
void waylandGrabKeyboard(void);
|
||||
void waylandGrabPointer(void);
|
||||
void waylandUngrabKeyboard(void);
|
||||
void waylandUngrabPointer(void);
|
||||
void waylandCapturePointer(void);
|
||||
void waylandUncapturePointer(void);
|
||||
void waylandRealignPointer(void);
|
||||
void waylandWarpPointer(int x, int y, bool exiting);
|
||||
void waylandGuestPointerUpdated(double x, double y, double localX, double localY);
|
||||
|
||||
// output module
|
||||
bool waylandOutputInit(void);
|
||||
void waylandOutputFree(void);
|
||||
void waylandOutputBind(uint32_t name, uint32_t version);
|
||||
void waylandOutputTryUnbind(uint32_t name);
|
||||
int32_t waylandOutputGetScale(struct wl_output * output);
|
||||
|
||||
// poll module
|
||||
bool waylandPollInit(void);
|
||||
void waylandWait(unsigned int time);
|
||||
bool waylandPollRegister(int fd, WaylandPollCallback callback, void * opaque, uint32_t events);
|
||||
bool waylandPollUnregister(int fd);
|
||||
|
||||
// registry module
|
||||
bool waylandRegistryInit(void);
|
||||
void waylandRegistryFree(void);
|
||||
|
||||
// shell module
|
||||
bool waylandShellInit(const char * title, bool fullscreen, bool maximize, bool borderless);
|
||||
void waylandShellAckConfigureIfNeeded(void);
|
||||
void waylandSetFullscreen(bool fs);
|
||||
bool waylandGetFullscreen(void);
|
||||
void waylandMinimize(void);
|
||||
|
||||
// window module
|
||||
bool waylandWindowInit(const char * title, bool fullscreen, bool maximize, bool borderless);
|
||||
void waylandWindowFree(void);
|
||||
void waylandWindowUpdateScale(void);
|
||||
void waylandSetWindowSize(int x, int y);
|
||||
bool waylandIsValidPointerPos(int x, int y);
|
||||
@@ -1,116 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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 "wayland.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "app.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
// Surface-handling listeners.
|
||||
|
||||
void waylandWindowUpdateScale(void)
|
||||
{
|
||||
int32_t maxScale = 0;
|
||||
struct SurfaceOutput * node;
|
||||
|
||||
wl_list_for_each(node, &wlWm.surfaceOutputs, link)
|
||||
{
|
||||
int32_t scale = waylandOutputGetScale(node->output);
|
||||
if (scale > maxScale)
|
||||
maxScale = scale;
|
||||
}
|
||||
|
||||
if (maxScale)
|
||||
{
|
||||
wlWm.scale = maxScale;
|
||||
wlWm.needsResize = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void wlSurfaceEnterHandler(void * data, struct wl_surface * surface, struct wl_output * output)
|
||||
{
|
||||
struct SurfaceOutput * node = malloc(sizeof(struct SurfaceOutput));
|
||||
node->output = output;
|
||||
wl_list_insert(&wlWm.surfaceOutputs, &node->link);
|
||||
waylandWindowUpdateScale();
|
||||
}
|
||||
|
||||
static void wlSurfaceLeaveHandler(void * data, struct wl_surface * surface, struct wl_output * output)
|
||||
{
|
||||
struct SurfaceOutput * node;
|
||||
wl_list_for_each(node, &wlWm.surfaceOutputs, link)
|
||||
if (node->output == output)
|
||||
{
|
||||
wl_list_remove(&node->link);
|
||||
break;
|
||||
}
|
||||
waylandWindowUpdateScale();
|
||||
}
|
||||
|
||||
static const struct wl_surface_listener wlSurfaceListener = {
|
||||
.enter = wlSurfaceEnterHandler,
|
||||
.leave = wlSurfaceLeaveHandler,
|
||||
};
|
||||
|
||||
bool waylandWindowInit(const char * title, bool fullscreen, bool maximize, bool borderless)
|
||||
{
|
||||
wlWm.scale = 1;
|
||||
|
||||
if (!wlWm.compositor)
|
||||
{
|
||||
DEBUG_ERROR("Compositor missing wl_compositor (version 3+), will not proceed");
|
||||
return false;
|
||||
}
|
||||
|
||||
wlWm.surface = wl_compositor_create_surface(wlWm.compositor);
|
||||
if (!wlWm.surface)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create wl_surface");
|
||||
return false;
|
||||
}
|
||||
|
||||
wl_surface_add_listener(wlWm.surface, &wlSurfaceListener, NULL);
|
||||
|
||||
if (!waylandShellInit(title, fullscreen, maximize, borderless))
|
||||
return false;
|
||||
|
||||
wl_surface_commit(wlWm.surface);
|
||||
return true;
|
||||
}
|
||||
|
||||
void waylandWindowFree(void)
|
||||
{
|
||||
wl_surface_destroy(wlWm.surface);
|
||||
}
|
||||
|
||||
void waylandSetWindowSize(int x, int y)
|
||||
{
|
||||
// FIXME: implement.
|
||||
}
|
||||
|
||||
bool waylandIsValidPointerPos(int x, int y)
|
||||
{
|
||||
return x >= 0 && x < wlWm.width && y >= 0 && y < wlWm.height;
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(displayserver_X11 LANGUAGES C)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(DISPLAYSERVER_X11_PKGCONFIG REQUIRED
|
||||
x11
|
||||
xi
|
||||
xfixes
|
||||
xscrnsaver
|
||||
xinerama
|
||||
)
|
||||
|
||||
add_library(displayserver_X11 STATIC
|
||||
x11.c
|
||||
atoms.c
|
||||
clipboard.c
|
||||
)
|
||||
|
||||
add_definitions(-D GLX_GLXEXT_PROTOTYPES)
|
||||
|
||||
target_link_libraries(displayserver_X11
|
||||
${DISPLAYSERVER_X11_PKGCONFIG_LIBRARIES}
|
||||
lg_common
|
||||
)
|
||||
|
||||
target_include_directories(displayserver_X11
|
||||
PRIVATE
|
||||
src
|
||||
${DISPLAYSERVER_X11_PKGCONFIG_INCLUDE_DIRS}
|
||||
)
|
||||
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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 "atoms.h"
|
||||
#include "x11.h"
|
||||
|
||||
struct X11DSAtoms x11atoms = { 0 };
|
||||
|
||||
void X11AtomsInit(void)
|
||||
{
|
||||
#define DEF_ATOM(x, onlyIfExists) \
|
||||
x11atoms.x = XInternAtom(x11.display, #x, onlyIfExists);
|
||||
DEF_ATOMS()
|
||||
#undef DEF_ATOM
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef _H_X11DS_ATOMS_
|
||||
#define _H_X11DS_ATOMS_
|
||||
|
||||
#define DEF_ATOMS() \
|
||||
DEF_ATOM(_NET_REQUEST_FRAME_EXTENTS, True) \
|
||||
DEF_ATOM(_NET_FRAME_EXTENTS, True) \
|
||||
DEF_ATOM(_NET_WM_BYPASS_COMPOSITOR, False) \
|
||||
DEF_ATOM(_NET_WM_STATE, True) \
|
||||
DEF_ATOM(_NET_WM_STATE_FULLSCREEN, True) \
|
||||
DEF_ATOM(_NET_WM_STATE_MAXIMIZED_HORZ, True) \
|
||||
DEF_ATOM(_NET_WM_STATE_MAXIMIZED_VERT, True) \
|
||||
DEF_ATOM(_NET_WM_WINDOW_TYPE, True) \
|
||||
DEF_ATOM(_NET_WM_WINDOW_TYPE_NORMAL, True) \
|
||||
DEF_ATOM(_NET_WM_WINDOW_TYPE_UTILITY, True) \
|
||||
DEF_ATOM(WM_DELETE_WINDOW, True) \
|
||||
DEF_ATOM(_MOTIF_WM_HINTS, True) \
|
||||
\
|
||||
DEF_ATOM(CLIPBOARD, False) \
|
||||
DEF_ATOM(TARGETS, False) \
|
||||
DEF_ATOM(SEL_DATA, False) \
|
||||
DEF_ATOM(INCR, False)
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
|
||||
#define DEF_ATOM(x, onlyIfExists) Atom x;
|
||||
struct X11DSAtoms
|
||||
{
|
||||
DEF_ATOMS()
|
||||
};
|
||||
#undef DEF_ATOM
|
||||
|
||||
extern struct X11DSAtoms x11atoms;
|
||||
|
||||
void X11AtomsInit(void);
|
||||
|
||||
#endif
|
||||
@@ -1,424 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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 "clipboard.h"
|
||||
#include "x11.h"
|
||||
#include "atoms.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xatom.h>
|
||||
|
||||
#include "app.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
struct X11ClipboardState
|
||||
{
|
||||
Atom aCurSelection;
|
||||
Atom aTypes[LG_CLIPBOARD_DATA_NONE];
|
||||
LG_ClipboardData type;
|
||||
bool haveRequest;
|
||||
|
||||
bool incrStart;
|
||||
unsigned int lowerBound;
|
||||
};
|
||||
|
||||
static const char * atomTypes[] =
|
||||
{
|
||||
"UTF8_STRING",
|
||||
"image/png",
|
||||
"image/bmp",
|
||||
"image/tiff",
|
||||
"image/jpeg"
|
||||
};
|
||||
|
||||
static struct X11ClipboardState x11cb;
|
||||
|
||||
// forwards
|
||||
static void x11CBSelectionRequest(const XSelectionRequestEvent e);
|
||||
static void x11CBSelectionClear(const XSelectionClearEvent e);
|
||||
static void x11CBSelectionIncr(const XPropertyEvent e);
|
||||
static void x11CBSelectionNotify(const XSelectionEvent e);
|
||||
static void x11CBXFixesSelectionNotify(const XFixesSelectionNotifyEvent e);
|
||||
|
||||
bool x11CBEventThread(const XEvent xe)
|
||||
{
|
||||
switch(xe.type)
|
||||
{
|
||||
case SelectionRequest:
|
||||
x11CBSelectionRequest(xe.xselectionrequest);
|
||||
return true;
|
||||
|
||||
case SelectionClear:
|
||||
x11CBSelectionClear(xe.xselectionclear);
|
||||
return true;
|
||||
|
||||
case SelectionNotify:
|
||||
x11CBSelectionNotify(xe.xselection);
|
||||
return true;
|
||||
|
||||
case PropertyNotify:
|
||||
if (xe.xproperty.state != PropertyNewValue)
|
||||
break;
|
||||
|
||||
if (xe.xproperty.atom == x11atoms.SEL_DATA)
|
||||
{
|
||||
if (x11cb.lowerBound == 0)
|
||||
return true;
|
||||
|
||||
x11CBSelectionIncr(xe.xproperty);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (xe.type == x11.eventBase + XFixesSelectionNotify)
|
||||
{
|
||||
XFixesSelectionNotifyEvent * sne = (XFixesSelectionNotifyEvent *)&xe;
|
||||
x11CBXFixesSelectionNotify(*sne);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool x11CBInit()
|
||||
{
|
||||
x11cb.aCurSelection = BadValue;
|
||||
for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
{
|
||||
x11cb.aTypes[i] = XInternAtom(x11.display, atomTypes[i], False);
|
||||
if (x11cb.aTypes[i] == BadAlloc || x11cb.aTypes[i] == BadValue)
|
||||
{
|
||||
DEBUG_ERROR("failed to get atom for type: %s", atomTypes[i]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// use xfixes to get clipboard change notifications
|
||||
if (!XFixesQueryExtension(x11.display, &x11.eventBase, &x11.errorBase))
|
||||
{
|
||||
DEBUG_ERROR("failed to initialize xfixes");
|
||||
return false;
|
||||
}
|
||||
|
||||
XFixesSelectSelectionInput(x11.display, x11.window,
|
||||
XA_PRIMARY, XFixesSetSelectionOwnerNotifyMask);
|
||||
XFixesSelectSelectionInput(x11.display, x11.window,
|
||||
x11atoms.CLIPBOARD, XFixesSetSelectionOwnerNotifyMask);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void x11CBReplyFn(void * opaque, LG_ClipboardData type,
|
||||
uint8_t * data, uint32_t size)
|
||||
{
|
||||
XEvent *s = (XEvent *)opaque;
|
||||
|
||||
XChangeProperty(
|
||||
x11.display ,
|
||||
s->xselection.requestor,
|
||||
s->xselection.property ,
|
||||
s->xselection.target ,
|
||||
8,
|
||||
PropModeReplace,
|
||||
data,
|
||||
size);
|
||||
|
||||
XSendEvent(x11.display, s->xselection.requestor, 0, 0, s);
|
||||
XFlush(x11.display);
|
||||
free(s);
|
||||
}
|
||||
|
||||
static void x11CBSelectionRequest(const XSelectionRequestEvent e)
|
||||
{
|
||||
XEvent * s = (XEvent *)malloc(sizeof(XEvent));
|
||||
s->xselection.type = SelectionNotify;
|
||||
s->xselection.requestor = e.requestor;
|
||||
s->xselection.selection = e.selection;
|
||||
s->xselection.target = e.target;
|
||||
s->xselection.property = e.property;
|
||||
s->xselection.time = e.time;
|
||||
|
||||
if (!x11cb.haveRequest)
|
||||
goto nodata;
|
||||
|
||||
// target list requested
|
||||
if (e.target == x11atoms.TARGETS)
|
||||
{
|
||||
Atom targets[2];
|
||||
targets[0] = x11atoms.TARGETS;
|
||||
targets[1] = x11cb.aTypes[x11cb.type];
|
||||
|
||||
XChangeProperty(
|
||||
e.display,
|
||||
e.requestor,
|
||||
e.property,
|
||||
XA_ATOM,
|
||||
32,
|
||||
PropModeReplace,
|
||||
(unsigned char*)targets,
|
||||
sizeof(targets) / sizeof(Atom));
|
||||
|
||||
goto send;
|
||||
}
|
||||
|
||||
// look to see if we can satisfy the data type
|
||||
for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
if (x11cb.aTypes[i] == e.target && x11cb.type == i)
|
||||
{
|
||||
// request the data
|
||||
app_clipboardRequest(x11CBReplyFn, s);
|
||||
return;
|
||||
}
|
||||
|
||||
nodata:
|
||||
// report no data
|
||||
s->xselection.property = None;
|
||||
|
||||
send:
|
||||
XSendEvent(x11.display, e.requestor, 0, 0, s);
|
||||
XFlush(x11.display);
|
||||
free(s);
|
||||
}
|
||||
|
||||
static void x11CBSelectionClear(const XSelectionClearEvent e)
|
||||
{
|
||||
if (e.selection != XA_PRIMARY && e.selection != x11atoms.CLIPBOARD)
|
||||
return;
|
||||
|
||||
x11cb.aCurSelection = BadValue;
|
||||
app_clipboardRelease();
|
||||
return;
|
||||
}
|
||||
|
||||
static void x11CBSelectionIncr(const XPropertyEvent e)
|
||||
{
|
||||
Atom type;
|
||||
int format;
|
||||
unsigned long itemCount, after;
|
||||
unsigned char *data;
|
||||
|
||||
if (XGetWindowProperty(
|
||||
e.display,
|
||||
e.window,
|
||||
e.atom,
|
||||
0, ~0L, // start and length
|
||||
True, // delete the property
|
||||
x11atoms.INCR,
|
||||
&type,
|
||||
&format,
|
||||
&itemCount,
|
||||
&after,
|
||||
&data) != Success)
|
||||
{
|
||||
DEBUG_INFO("GetProp Failed");
|
||||
app_clipboardNotifySize(LG_CLIPBOARD_DATA_NONE, 0);
|
||||
goto out;
|
||||
}
|
||||
|
||||
LG_ClipboardData dataType;
|
||||
for(dataType = 0; dataType < LG_CLIPBOARD_DATA_NONE; ++dataType)
|
||||
if (x11cb.aTypes[dataType] == type)
|
||||
break;
|
||||
|
||||
if (dataType == LG_CLIPBOARD_DATA_NONE)
|
||||
{
|
||||
DEBUG_WARN("clipboard data (%s) not in a supported format",
|
||||
XGetAtomName(x11.display, type));
|
||||
|
||||
x11cb.lowerBound = 0;
|
||||
app_clipboardNotifySize(LG_CLIPBOARD_DATA_NONE, 0);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (x11cb.incrStart)
|
||||
{
|
||||
app_clipboardNotifySize(dataType, x11cb.lowerBound);
|
||||
x11cb.incrStart = false;
|
||||
}
|
||||
|
||||
XFree(data);
|
||||
data = NULL;
|
||||
|
||||
if (XGetWindowProperty(
|
||||
e.display,
|
||||
e.window,
|
||||
e.atom,
|
||||
0, ~0L, // start and length
|
||||
True, // delete the property
|
||||
type,
|
||||
&type,
|
||||
&format,
|
||||
&itemCount,
|
||||
&after,
|
||||
&data) != Success)
|
||||
{
|
||||
DEBUG_ERROR("XGetWindowProperty Failed");
|
||||
app_clipboardNotifySize(LG_CLIPBOARD_DATA_NONE, 0);
|
||||
goto out;
|
||||
}
|
||||
|
||||
app_clipboardData(dataType, data, itemCount);
|
||||
x11cb.lowerBound -= itemCount;
|
||||
|
||||
out:
|
||||
if (data)
|
||||
XFree(data);
|
||||
}
|
||||
|
||||
static void x11CBXFixesSelectionNotify(const XFixesSelectionNotifyEvent e)
|
||||
{
|
||||
// check if the selection is valid and it isn't ourself
|
||||
if ((e.selection != XA_PRIMARY && e.selection != x11atoms.CLIPBOARD) ||
|
||||
e.owner == x11.window || e.owner == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// remember which selection we are working with
|
||||
x11cb.aCurSelection = e.selection;
|
||||
XConvertSelection(
|
||||
x11.display,
|
||||
e.selection,
|
||||
x11atoms.TARGETS,
|
||||
x11atoms.TARGETS,
|
||||
x11.window,
|
||||
CurrentTime);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void x11CBSelectionNotify(const XSelectionEvent e)
|
||||
{
|
||||
if (e.property == None)
|
||||
return;
|
||||
|
||||
Atom type;
|
||||
int format;
|
||||
unsigned long itemCount, after;
|
||||
unsigned char *data;
|
||||
|
||||
if (XGetWindowProperty(
|
||||
e.display,
|
||||
e.requestor,
|
||||
e.property,
|
||||
0, ~0L, // start and length
|
||||
True , // delete the property
|
||||
AnyPropertyType,
|
||||
&type,
|
||||
&format,
|
||||
&itemCount,
|
||||
&after,
|
||||
&data) != Success)
|
||||
{
|
||||
app_clipboardNotifySize(LG_CLIPBOARD_DATA_NONE, 0);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (type == x11atoms.INCR)
|
||||
{
|
||||
x11cb.incrStart = true;
|
||||
x11cb.lowerBound = *(unsigned int *)data;
|
||||
goto out;
|
||||
}
|
||||
|
||||
// the target list
|
||||
if (e.property == x11atoms.TARGETS)
|
||||
{
|
||||
// the format is 32-bit and we must have data
|
||||
// this is technically incorrect however as it's
|
||||
// an array of padded 64-bit values
|
||||
if (!data || format != 32)
|
||||
goto out;
|
||||
|
||||
int typeCount = 0;
|
||||
LG_ClipboardData types[itemCount];
|
||||
|
||||
// see if we support any of the targets listed
|
||||
const uint64_t * targets = (const uint64_t *)data;
|
||||
for(unsigned long i = 0; i < itemCount; ++i)
|
||||
{
|
||||
for(int n = 0; n < LG_CLIPBOARD_DATA_NONE; ++n)
|
||||
if (x11cb.aTypes[n] == targets[i])
|
||||
types[typeCount++] = n;
|
||||
}
|
||||
|
||||
app_clipboardNotifyTypes(types, typeCount);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (e.property == x11atoms.SEL_DATA)
|
||||
{
|
||||
LG_ClipboardData dataType;
|
||||
for(dataType = 0; dataType < LG_CLIPBOARD_DATA_NONE; ++dataType)
|
||||
if (x11cb.aTypes[dataType] == type)
|
||||
break;
|
||||
|
||||
if (dataType == LG_CLIPBOARD_DATA_NONE)
|
||||
{
|
||||
DEBUG_WARN("clipboard data (%s) not in a supported format",
|
||||
XGetAtomName(x11.display, type));
|
||||
goto out;
|
||||
}
|
||||
|
||||
app_clipboardData(dataType, data, itemCount);
|
||||
goto out;
|
||||
}
|
||||
|
||||
out:
|
||||
if (data)
|
||||
XFree(data);
|
||||
}
|
||||
|
||||
void x11CBNotice(LG_ClipboardData type)
|
||||
{
|
||||
x11cb.haveRequest = true;
|
||||
x11cb.type = type;
|
||||
XSetSelectionOwner(x11.display, XA_PRIMARY , x11.window, CurrentTime);
|
||||
XSetSelectionOwner(x11.display, x11atoms.CLIPBOARD, x11.window, CurrentTime);
|
||||
XFlush(x11.display);
|
||||
}
|
||||
|
||||
void x11CBRelease(void)
|
||||
{
|
||||
x11cb.haveRequest = false;
|
||||
XSetSelectionOwner(x11.display, XA_PRIMARY , None, CurrentTime);
|
||||
XSetSelectionOwner(x11.display, x11atoms.CLIPBOARD, None, CurrentTime);
|
||||
XFlush(x11.display);
|
||||
}
|
||||
|
||||
void x11CBRequest(LG_ClipboardData type)
|
||||
{
|
||||
if (x11cb.aCurSelection == BadValue)
|
||||
return;
|
||||
|
||||
XConvertSelection(
|
||||
x11.display,
|
||||
x11cb.aCurSelection,
|
||||
x11cb.aTypes[type],
|
||||
x11atoms.SEL_DATA,
|
||||
x11.window,
|
||||
CurrentTime);
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef _H_X11DS_CLIPBOARD_
|
||||
#define _H_X11DS_CLIPBOARD_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <X11/extensions/Xfixes.h>
|
||||
|
||||
#include "interface/displayserver.h"
|
||||
|
||||
bool x11CBEventThread(const XEvent xe);
|
||||
|
||||
bool x11CBInit();
|
||||
void x11CBNotice(LG_ClipboardData type);
|
||||
void x11CBRelease(void);
|
||||
void x11CBRequest(LG_ClipboardData type);
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,64 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef _H_X11DS_X11_
|
||||
#define _H_X11DS_X11_
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xatom.h>
|
||||
#include <X11/Xutil.h>
|
||||
|
||||
#include "common/thread.h"
|
||||
#include "common/types.h"
|
||||
|
||||
struct X11DSState
|
||||
{
|
||||
Display * display;
|
||||
Window window;
|
||||
XVisualInfo * visual;
|
||||
int xinputOp;
|
||||
|
||||
LGThread * eventThread;
|
||||
|
||||
int pointerDev;
|
||||
int keyboardDev;
|
||||
int xValuator;
|
||||
int yValuator;
|
||||
|
||||
bool pointerGrabbed;
|
||||
bool keyboardGrabbed;
|
||||
bool entered;
|
||||
bool focused;
|
||||
bool fullscreen;
|
||||
|
||||
struct Rect rect;
|
||||
struct Border border;
|
||||
|
||||
Cursor blankCursor;
|
||||
Cursor squareCursor;
|
||||
|
||||
// XFixes vars
|
||||
int eventBase;
|
||||
int errorBase;
|
||||
};
|
||||
|
||||
extern struct X11DSState x11;
|
||||
|
||||
#endif
|
||||
@@ -19,7 +19,7 @@ function(add_font name)
|
||||
endfunction()
|
||||
|
||||
# Add/remove fonts here!
|
||||
add_font(freetype)
|
||||
add_font(SDL)
|
||||
|
||||
list(REMOVE_AT FONTS 0)
|
||||
list(REMOVE_AT FONTS_LINK 0)
|
||||
|
||||
26
client/fonts/SDL/CMakeLists.txt
Normal file
26
client/fonts/SDL/CMakeLists.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(font_SDL LANGUAGES C)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(FONT_SDL_PKGCONFIG REQUIRED
|
||||
SDL2_ttf
|
||||
fontconfig
|
||||
)
|
||||
|
||||
add_library(font_SDL STATIC
|
||||
src/sdl.c
|
||||
)
|
||||
|
||||
target_link_libraries(font_SDL
|
||||
${FONT_SDL_PKGCONFIG_LIBRARIES}
|
||||
lg_common
|
||||
)
|
||||
|
||||
target_include_directories(font_SDL
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
PRIVATE
|
||||
src
|
||||
${FONT_SDL_PKGCONFIG_INCLUDE_DIRS}
|
||||
)
|
||||
153
client/fonts/SDL/src/sdl.c
Normal file
153
client/fonts/SDL/src/sdl.c
Normal file
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
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 <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "interface/font.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
#include <SDL2/SDL_ttf.h>
|
||||
#include <fontconfig/fontconfig.h>
|
||||
|
||||
static int g_initCount = 0;
|
||||
static FcConfig * g_fontConfig = NULL;
|
||||
|
||||
struct Inst
|
||||
{
|
||||
TTF_Font * font;
|
||||
};
|
||||
|
||||
static bool lgf_sdl_create(LG_FontObj * opaque, const char * font_name, unsigned int size)
|
||||
{
|
||||
if (g_initCount++ == 0)
|
||||
{
|
||||
if (TTF_Init() < 0)
|
||||
{
|
||||
DEBUG_ERROR("TTF_Init Failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
g_fontConfig = FcInitLoadConfigAndFonts();
|
||||
if (!g_fontConfig)
|
||||
{
|
||||
DEBUG_ERROR("FcInitLoadConfigAndFonts Failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
*opaque = malloc(sizeof(struct Inst));
|
||||
if (!*opaque)
|
||||
{
|
||||
DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct Inst));
|
||||
return false;
|
||||
}
|
||||
memset(*opaque, 0, sizeof(struct Inst));
|
||||
|
||||
struct Inst * this = (struct Inst *)*opaque;
|
||||
|
||||
if (!font_name)
|
||||
font_name = "FreeMono";
|
||||
|
||||
FcPattern * pat = FcNameParse((const FcChar8*)font_name);
|
||||
FcConfigSubstitute (g_fontConfig, pat, FcMatchPattern);
|
||||
FcDefaultSubstitute(pat);
|
||||
FcResult result;
|
||||
FcChar8 * file = NULL;
|
||||
FcPattern * font = FcFontMatch(g_fontConfig, pat, &result);
|
||||
|
||||
if (font && (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch))
|
||||
{
|
||||
this->font = TTF_OpenFont((char *)file, size);
|
||||
if (!this->font)
|
||||
{
|
||||
DEBUG_ERROR("TTL_OpenFont Failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_ERROR("Failed to locate the requested font: %s", font_name);
|
||||
return false;
|
||||
}
|
||||
FcPatternDestroy(pat);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void lgf_sdl_destroy(LG_FontObj opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
if (this->font)
|
||||
TTF_CloseFont(this->font);
|
||||
free(this);
|
||||
|
||||
if (--g_initCount == 0)
|
||||
TTF_Quit();
|
||||
}
|
||||
|
||||
static LG_FontBitmap * lgf_sdl_render(LG_FontObj opaque, unsigned int fg_color, const char * text)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
SDL_Surface * surface;
|
||||
SDL_Color color;
|
||||
color.r = (fg_color & 0xff000000) >> 24;
|
||||
color.g = (fg_color & 0x00ff0000) >> 16;
|
||||
color.b = (fg_color & 0x0000ff00) >> 8;
|
||||
color.a = (fg_color & 0x000000ff) >> 0;
|
||||
|
||||
if (!(surface = TTF_RenderText_Blended(this->font, text, color)))
|
||||
{
|
||||
DEBUG_ERROR("Failed to render text: %s", TTF_GetError());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LG_FontBitmap * out = malloc(sizeof(LG_FontBitmap));
|
||||
if (!out)
|
||||
{
|
||||
SDL_FreeSurface(surface);
|
||||
DEBUG_ERROR("Failed to allocate memory for font bitmap");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
out->reserved = surface;
|
||||
out->width = surface->w;
|
||||
out->height = surface->h;
|
||||
out->bpp = surface->format->BytesPerPixel;
|
||||
out->pixels = surface->pixels;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static void lgf_sdl_release(LG_FontObj opaque, LG_FontBitmap * font)
|
||||
{
|
||||
LG_FontBitmap * bitmap = (LG_FontBitmap *)font;
|
||||
SDL_FreeSurface(bitmap->reserved);
|
||||
free(bitmap);
|
||||
}
|
||||
|
||||
struct LG_Font LGF_SDL =
|
||||
{
|
||||
.name = "SDL",
|
||||
.create = lgf_sdl_create,
|
||||
.destroy = lgf_sdl_destroy,
|
||||
.render = lgf_sdl_render,
|
||||
.release = lgf_sdl_release
|
||||
};
|
||||
@@ -1,26 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(font_freetype LANGUAGES C)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(FONT_FREETYPE_PKGCONFIG REQUIRED
|
||||
freetype2
|
||||
fontconfig
|
||||
)
|
||||
|
||||
add_library(font_freetype STATIC
|
||||
src/freetype.c
|
||||
)
|
||||
|
||||
target_link_libraries(font_freetype
|
||||
${FONT_FREETYPE_PKGCONFIG_LIBRARIES}
|
||||
lg_common
|
||||
)
|
||||
|
||||
target_include_directories(font_freetype
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
PRIVATE
|
||||
src
|
||||
${FONT_FREETYPE_PKGCONFIG_INCLUDE_DIRS}
|
||||
)
|
||||
@@ -1,325 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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 <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "interface/font.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
#include <fontconfig/fontconfig.h>
|
||||
|
||||
static int g_initCount = 0;
|
||||
static FcConfig * g_fontConfig = NULL;
|
||||
static FT_Library g_ft;
|
||||
|
||||
struct Inst
|
||||
{
|
||||
FT_Face face;
|
||||
unsigned int height;
|
||||
};
|
||||
|
||||
static bool lgf_freetype_create(LG_FontObj * opaque, const char * font_name, unsigned int size)
|
||||
{
|
||||
bool ret = false;
|
||||
|
||||
if (g_initCount == 0)
|
||||
{
|
||||
if (FT_Init_FreeType(&g_ft))
|
||||
{
|
||||
DEBUG_ERROR("FT_Init_FreeType Failed");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
g_fontConfig = FcInitLoadConfigAndFonts();
|
||||
if (!g_fontConfig)
|
||||
{
|
||||
DEBUG_ERROR("FcInitLoadConfigAndFonts Failed");
|
||||
goto fail_init;
|
||||
}
|
||||
}
|
||||
|
||||
*opaque = malloc(sizeof(struct Inst));
|
||||
if (!*opaque)
|
||||
{
|
||||
DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct Inst));
|
||||
goto fail_config;
|
||||
}
|
||||
|
||||
memset(*opaque, 0, sizeof(struct Inst));
|
||||
struct Inst * this = (struct Inst *)*opaque;
|
||||
|
||||
if (!font_name)
|
||||
font_name = "FreeMono";
|
||||
|
||||
FcPattern * pat = FcNameParse((const FcChar8*)font_name);
|
||||
if (!pat)
|
||||
{
|
||||
DEBUG_ERROR("FCNameParse failed");
|
||||
goto fail_opaque;
|
||||
}
|
||||
|
||||
FcConfigSubstitute(g_fontConfig, pat, FcMatchPattern);
|
||||
FcDefaultSubstitute(pat);
|
||||
FcResult result;
|
||||
FcChar8 * file = NULL;
|
||||
FcPattern * match = FcFontMatch(g_fontConfig, pat, &result);
|
||||
|
||||
if (!match)
|
||||
{
|
||||
DEBUG_ERROR("FcFontMatch Failed");
|
||||
goto fail_parse;
|
||||
}
|
||||
|
||||
if (FcPatternGetString(match, FC_FILE, 0, &file) == FcResultMatch)
|
||||
{
|
||||
if (FT_New_Face(g_ft, (char *) file, 0, &this->face))
|
||||
{
|
||||
DEBUG_ERROR("FT_New_Face Failed");
|
||||
goto fail_match;
|
||||
}
|
||||
|
||||
if (FT_Select_Charmap(this->face, ft_encoding_unicode))
|
||||
{
|
||||
DEBUG_ERROR("FT_Select_Charmap failed");
|
||||
FT_Done_Face(this->face);
|
||||
goto fail_match;
|
||||
}
|
||||
|
||||
FT_Set_Pixel_Sizes(this->face, 0, size);
|
||||
this->height = size;
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_ERROR("Failed to locate the requested font: %s", font_name);
|
||||
goto fail_match;
|
||||
}
|
||||
|
||||
++g_initCount;
|
||||
ret = true;
|
||||
|
||||
fail_match:
|
||||
FcPatternDestroy(match);
|
||||
|
||||
fail_parse:
|
||||
FcPatternDestroy(pat);
|
||||
|
||||
if (ret)
|
||||
return true;
|
||||
|
||||
fail_opaque:
|
||||
free(this);
|
||||
*opaque = NULL;
|
||||
|
||||
fail_config:
|
||||
if (g_initCount == 0)
|
||||
{
|
||||
FcConfigDestroy(g_fontConfig);
|
||||
g_fontConfig = NULL;
|
||||
}
|
||||
|
||||
fail_init:
|
||||
if (g_initCount == 0)
|
||||
FT_Done_FreeType(g_ft);
|
||||
|
||||
fail:
|
||||
return false;
|
||||
}
|
||||
|
||||
static void lgf_freetype_destroy(LG_FontObj opaque)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
if (this->face)
|
||||
FT_Done_Face(this->face);
|
||||
free(this);
|
||||
|
||||
if (--g_initCount == 0)
|
||||
{
|
||||
FcConfigDestroy(g_fontConfig);
|
||||
g_fontConfig = NULL;
|
||||
|
||||
FT_Done_FreeType(g_ft);
|
||||
}
|
||||
}
|
||||
|
||||
// A very simple UTF-8 decoder that assumes the input is valid.
|
||||
static unsigned int utf8_decode(const char * str)
|
||||
{
|
||||
const unsigned char * ptr = (const unsigned char *) str;
|
||||
// Handle the 4 byte case: 1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx.
|
||||
if ((*ptr & 0xf8) == 0xf0)
|
||||
return (ptr[0] & 0x07) << 18 | (ptr[1] & 0x3f) << 12 | (ptr[2] & 0x3f) << 6 | (ptr[3] & 0x3f);
|
||||
// Handle the 3 byte case: 1110 xxxx 10xx xxxx 10xx xxxx.
|
||||
else if ((*ptr & 0xf0) == 0xe0)
|
||||
return (ptr[0] & 0x0f) << 12 | (ptr[1] & 0x3f) << 6 | (ptr[2] & 0x3f);
|
||||
// Handle the 2 byte case: 110x xxxx 10xx xxxx.
|
||||
else if ((*ptr & 0xe0) == 0xc0)
|
||||
return (ptr[0] & 0x1f) << 6 | (ptr[1] & 0x3f);
|
||||
// Everything else is the 1 byte case.
|
||||
else
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
// Return the length of the current UTF-8 character. Assumes the input is valid.
|
||||
static unsigned int utf8_advance(const char * str)
|
||||
{
|
||||
const unsigned char * ptr = (const unsigned char *) str;
|
||||
// 4 byte case starts with 1111 0xxx.
|
||||
if ((*ptr & 0xf8) == 0xf0)
|
||||
return 4;
|
||||
// 3 byte case starts with 1110 xxxx.
|
||||
else if ((*ptr & 0xf0) == 0xe0)
|
||||
return 3;
|
||||
// 2 byte case starts with 110x xxxx.
|
||||
else if ((*ptr & 0xe0) == 0xc0)
|
||||
return 2;
|
||||
// Everything else is the 1 byte case.
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
static LG_FontBitmap * lgf_freetype_render(LG_FontObj opaque, unsigned int fg_color, const char * text)
|
||||
{
|
||||
struct Inst * this = (struct Inst *)opaque;
|
||||
|
||||
int width = 0;
|
||||
int row = 0;
|
||||
int rowWidth = 0;
|
||||
int topAscend = 0;
|
||||
int bottomDescend = 0;
|
||||
|
||||
for (const char * ptr = text; *ptr; ptr += utf8_advance(ptr))
|
||||
{
|
||||
unsigned int ch = utf8_decode(ptr);
|
||||
if (ch == '\n')
|
||||
{
|
||||
if (!ptr[1])
|
||||
break;
|
||||
if (rowWidth > width)
|
||||
width = rowWidth;
|
||||
rowWidth = bottomDescend = 0;
|
||||
++row;
|
||||
}
|
||||
else if (FT_Load_Char(this->face, ch, FT_LOAD_RENDER))
|
||||
{
|
||||
DEBUG_ERROR("Failed to load character: %c", *ptr);
|
||||
return NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
FT_GlyphSlot glyph = this->face->glyph;
|
||||
rowWidth += glyph->advance.x / 64;
|
||||
|
||||
int descend = glyph->bitmap.rows - glyph->bitmap_top;
|
||||
if (descend > bottomDescend)
|
||||
bottomDescend = descend;
|
||||
if (row == 0 && glyph->bitmap_top > topAscend)
|
||||
topAscend = glyph->bitmap_top;
|
||||
}
|
||||
}
|
||||
|
||||
if (rowWidth > width)
|
||||
width = rowWidth;
|
||||
|
||||
int height = topAscend + this->height * row + bottomDescend;
|
||||
uint32_t * pixels = calloc(width * height, sizeof(uint32_t));
|
||||
|
||||
if (!pixels)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate memory for font pixels");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int baseline = topAscend;
|
||||
int x = 0;
|
||||
|
||||
unsigned int r = (fg_color & 0xff000000) >> 24;
|
||||
unsigned int g = (fg_color & 0x00ff0000) >> 16;
|
||||
unsigned int b = (fg_color & 0x0000ff00) >> 8;
|
||||
uint32_t color = (r << 0) | (g << 8) | (b << 16);
|
||||
|
||||
for (const char * ptr = text; *ptr; ptr += utf8_advance(ptr))
|
||||
{
|
||||
unsigned int ch = utf8_decode(ptr);
|
||||
if (ch == '\n')
|
||||
{
|
||||
baseline += this->height;
|
||||
x = 0;
|
||||
}
|
||||
else if (FT_Load_Char(this->face, ch, FT_LOAD_RENDER))
|
||||
{
|
||||
DEBUG_ERROR("Failed to load character: U+%x", ch);
|
||||
return NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
FT_GlyphSlot glyph = this->face->glyph;
|
||||
int start = baseline - glyph->bitmap_top;
|
||||
int pitch = width;
|
||||
|
||||
if (glyph->bitmap.pitch < 0)
|
||||
{
|
||||
start += glyph->bitmap.rows - 1;
|
||||
pitch = -pitch;
|
||||
}
|
||||
|
||||
for (int i = 0; i < glyph->bitmap.rows; ++i)
|
||||
for (int j = 0; j < glyph->bitmap.width; ++j)
|
||||
pixels[(start + i) * pitch + x + j + glyph->bitmap_left] = color |
|
||||
(uint32_t)glyph->bitmap.buffer[i * glyph->bitmap.pitch + j] << 24;
|
||||
|
||||
x += glyph->advance.x / 64;
|
||||
}
|
||||
}
|
||||
|
||||
LG_FontBitmap * out = malloc(sizeof(LG_FontBitmap));
|
||||
if (!out)
|
||||
{
|
||||
free(pixels);
|
||||
DEBUG_ERROR("Failed to allocate memory for font bitmap");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
out->width = width;
|
||||
out->height = height;
|
||||
out->bpp = 4;
|
||||
out->pixels = (uint8_t *) pixels;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static void lgf_freetype_release(LG_FontObj opaque, LG_FontBitmap * font)
|
||||
{
|
||||
LG_FontBitmap * bitmap = (LG_FontBitmap *)font;
|
||||
free(bitmap->pixels);
|
||||
free(bitmap);
|
||||
}
|
||||
|
||||
struct LG_Font LGF_freetype =
|
||||
{
|
||||
.name = "freetype",
|
||||
.create = lgf_freetype_create,
|
||||
.destroy = lgf_freetype_destroy,
|
||||
.render = lgf_freetype_render,
|
||||
.release = lgf_freetype_release
|
||||
};
|
||||
@@ -1,129 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef _H_LG_APP_
|
||||
#define _H_LG_APP_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <linux/input.h>
|
||||
|
||||
#include "common/types.h"
|
||||
#include "interface/displayserver.h"
|
||||
|
||||
typedef enum LG_MsgAlert
|
||||
{
|
||||
LG_ALERT_INFO ,
|
||||
LG_ALERT_SUCCESS,
|
||||
LG_ALERT_WARNING,
|
||||
LG_ALERT_ERROR
|
||||
}
|
||||
LG_MsgAlert;
|
||||
|
||||
bool app_isRunning(void);
|
||||
bool app_inputEnabled(void);
|
||||
bool app_isCaptureMode(void);
|
||||
bool app_isCaptureOnlyMode(void);
|
||||
bool app_isFormatValid(void);
|
||||
void app_updateCursorPos(double x, double y);
|
||||
void app_updateWindowPos(int x, int y);
|
||||
void app_handleResizeEvent(int w, int h, double scale, const struct Border border);
|
||||
|
||||
void app_handleMouseRelative(double normx, double normy,
|
||||
double rawx, double rawy);
|
||||
|
||||
void app_handleMouseBasic(void);
|
||||
void app_resyncMouseBasic(void);
|
||||
|
||||
void app_handleButtonPress(int button);
|
||||
void app_handleButtonRelease(int button);
|
||||
void app_handleKeyPress(int scancode);
|
||||
void app_handleKeyRelease(int scancode);
|
||||
void app_handleEnterEvent(bool entered);
|
||||
void app_handleFocusEvent(bool focused);
|
||||
void app_handleCloseEvent(void);
|
||||
void app_handleRenderEvent(const uint64_t timeUs);
|
||||
|
||||
void app_setFullscreen(bool fs);
|
||||
bool app_getFullscreen(void);
|
||||
bool app_getProp(LG_DSProperty prop, void * ret);
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
EGLDisplay app_getEGLDisplay(void);
|
||||
EGLNativeWindowType app_getEGLNativeWindow(void);
|
||||
void app_eglSwapBuffers(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
LG_DSGLContext app_glCreateContext(void);
|
||||
void app_glDeleteContext(LG_DSGLContext context);
|
||||
void app_glMakeCurrent(LG_DSGLContext context);
|
||||
void app_glSetSwapInterval(int interval);
|
||||
void app_glSwapBuffers(void);
|
||||
#endif
|
||||
|
||||
void app_clipboardRelease(void);
|
||||
void app_clipboardNotifyTypes(const LG_ClipboardData types[], int count);
|
||||
void app_clipboardNotifySize(const LG_ClipboardData type, size_t size);
|
||||
void app_clipboardData(const LG_ClipboardData type, uint8_t * data, size_t size);
|
||||
void app_clipboardRequest(const LG_ClipboardReplyFn replyFn, void * opaque);
|
||||
|
||||
/**
|
||||
* Show an alert on screen
|
||||
* @param type The alert type
|
||||
* param fmt The alert message format
|
||||
@ param ... formatted message values
|
||||
*/
|
||||
void app_alert(LG_MsgAlert type, const char * fmt, ...);
|
||||
|
||||
typedef struct KeybindHandle * KeybindHandle;
|
||||
typedef void (*KeybindFn)(int sc, void * opaque);
|
||||
|
||||
/**
|
||||
* Register a handler for the <super>+<key> combination
|
||||
* @param sc The scancode to register
|
||||
* @param callback The function to be called when the combination is pressed
|
||||
* @param opaque A pointer to be passed to the callback, may be NULL
|
||||
* @retval A handle for the binding or NULL on failure.
|
||||
* The caller is required to release the handle via `app_releaseKeybind` when it is no longer required
|
||||
*/
|
||||
KeybindHandle app_registerKeybind(int sc, KeybindFn callback, void * opaque, const char * description);
|
||||
|
||||
/**
|
||||
* Release an existing key binding
|
||||
* @param handle A pointer to the keybind handle to release, may be NULL
|
||||
*/
|
||||
void app_releaseKeybind(KeybindHandle * handle);
|
||||
|
||||
/**
|
||||
* Release all keybindings
|
||||
*/
|
||||
void app_releaseAllKeybinds(void);
|
||||
|
||||
/**
|
||||
* Changes whether the help message is displayed or not.
|
||||
*/
|
||||
void app_showHelp(bool show);
|
||||
|
||||
/**
|
||||
* Changes whether the FPS is displayed or not.
|
||||
*/
|
||||
void app_showFPS(bool showFPS);
|
||||
|
||||
#endif
|
||||
@@ -1,52 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef _H_LG_EGL_DYNPROCS_
|
||||
#define _H_LG_EGL_DYNPROCS_
|
||||
#ifdef ENABLE_EGL
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <GL/gl.h>
|
||||
|
||||
typedef EGLDisplay (*eglGetPlatformDisplayEXT_t)(EGLenum platform,
|
||||
void *native_display, const EGLint *attrib_list);
|
||||
typedef void (*eglSwapBuffersWithDamageKHR_t)(EGLDisplay dpy,
|
||||
EGLSurface surface, const EGLint *rects, EGLint n_rects);
|
||||
typedef void (*glEGLImageTargetTexture2DOES_t)(GLenum target,
|
||||
GLeglImageOES image);
|
||||
|
||||
struct EGLDynProcs
|
||||
{
|
||||
eglGetPlatformDisplayEXT_t eglGetPlatformDisplay;
|
||||
eglGetPlatformDisplayEXT_t eglGetPlatformDisplayEXT;
|
||||
eglSwapBuffersWithDamageKHR_t eglSwapBuffersWithDamageKHR;
|
||||
eglSwapBuffersWithDamageKHR_t eglSwapBuffersWithDamageEXT;
|
||||
glEGLImageTargetTexture2DOES_t glEGLImageTargetTexture2DOES;
|
||||
};
|
||||
|
||||
extern struct EGLDynProcs g_egl_dynProcs;
|
||||
|
||||
void egl_dynProcsInit(void);
|
||||
|
||||
#else
|
||||
#define egl_dynProcsInit(...)
|
||||
#endif
|
||||
|
||||
#endif // _H_LG_EGL_DYNPROCS_
|
||||
58
client/include/interface/app.h
Normal file
58
client/include/interface/app.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
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 <SDL2/SDL.h>
|
||||
|
||||
typedef enum LG_MsgAlert
|
||||
{
|
||||
LG_ALERT_INFO ,
|
||||
LG_ALERT_SUCCESS,
|
||||
LG_ALERT_WARNING,
|
||||
LG_ALERT_ERROR
|
||||
}
|
||||
LG_MsgAlert;
|
||||
|
||||
typedef struct KeybindHandle * KeybindHandle;
|
||||
typedef void (*SuperEventFn)(SDL_Scancode key, void * opaque);
|
||||
|
||||
/**
|
||||
* Show an alert on screen
|
||||
* @param type The alert type
|
||||
* param fmt The alert message format
|
||||
@ param ... formatted message values
|
||||
*/
|
||||
void app_alert(LG_MsgAlert type, const char * fmt, ...);
|
||||
|
||||
/**
|
||||
* Register a handler for the <super>+<key> combination
|
||||
* @param key The scancode to register
|
||||
* @param callback The function to be called when the combination is pressed
|
||||
* @param opaque A pointer to be passed to the callback, may be NULL
|
||||
* @retval A handle for the binding or NULL on failure.
|
||||
* The caller is required to release the handle via `app_release_keybind` when it is no longer required
|
||||
*/
|
||||
KeybindHandle app_register_keybind(SDL_Scancode key, SuperEventFn callback, void * opaque);
|
||||
|
||||
/**
|
||||
* Release an existing key binding
|
||||
* @param handle A pointer to the keybind handle to release, may be NULL
|
||||
*/
|
||||
void app_release_keybind(KeybindHandle * handle);
|
||||
62
client/include/interface/clipboard.h
Normal file
62
client/include/interface/clipboard.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
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>
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_syswm.h>
|
||||
|
||||
typedef enum LG_ClipboardData
|
||||
{
|
||||
LG_CLIPBOARD_DATA_TEXT = 0,
|
||||
LG_CLIPBOARD_DATA_PNG,
|
||||
LG_CLIPBOARD_DATA_BMP,
|
||||
LG_CLIPBOARD_DATA_TIFF,
|
||||
LG_CLIPBOARD_DATA_JPEG,
|
||||
|
||||
LG_CLIPBOARD_DATA_NONE // enum max, not a data type
|
||||
}
|
||||
LG_ClipboardData;
|
||||
|
||||
typedef void (* LG_ClipboardReplyFn )(void * opaque, const LG_ClipboardData type, uint8_t * data, uint32_t size);
|
||||
typedef void (* LG_ClipboardRequestFn)(LG_ClipboardReplyFn replyFn, void * opaque);
|
||||
typedef void (* LG_ClipboardReleaseFn)();
|
||||
typedef void (* LG_ClipboardNotifyFn)(LG_ClipboardData type);
|
||||
typedef void (* LG_ClipboardDataFn )(const LG_ClipboardData type, uint8_t * data, size_t size);
|
||||
|
||||
typedef const char * (* LG_ClipboardGetName)();
|
||||
typedef bool (* LG_ClipboardInit)(SDL_SysWMinfo * wminfo, LG_ClipboardReleaseFn releaseFn, LG_ClipboardNotifyFn notifyFn, LG_ClipboardDataFn dataFn);
|
||||
typedef void (* LG_ClipboardFree)();
|
||||
typedef void (* LG_ClipboardWMEvent)(SDL_SysWMmsg * msg);
|
||||
typedef void (* LG_ClipboardNotice)(LG_ClipboardRequestFn requestFn, LG_ClipboardData type);
|
||||
typedef void (* LG_ClipboardRelease)();
|
||||
typedef void (* LG_ClipboardRequest)(LG_ClipboardData type);
|
||||
|
||||
typedef struct LG_Clipboard
|
||||
{
|
||||
LG_ClipboardGetName getName;
|
||||
LG_ClipboardInit init;
|
||||
LG_ClipboardFree free;
|
||||
LG_ClipboardWMEvent wmevent;
|
||||
LG_ClipboardNotice notice;
|
||||
LG_ClipboardRelease release;
|
||||
LG_ClipboardRequest request;
|
||||
}
|
||||
LG_Clipboard;
|
||||
75
client/include/interface/decoder.h
Normal file
75
client/include/interface/decoder.h
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
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 "renderer.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include <GL/gl.h>
|
||||
|
||||
typedef enum LG_OutFormat
|
||||
{
|
||||
LG_OUTPUT_INVALID,
|
||||
|
||||
LG_OUTPUT_BGRA,
|
||||
LG_OUTPUT_RGBA,
|
||||
LG_OUTPUT_RGBA10,
|
||||
LG_OUTPUT_YUV420
|
||||
}
|
||||
LG_OutFormat;
|
||||
|
||||
typedef bool (* LG_DecoderCreate )(void ** opaque);
|
||||
typedef void (* LG_DecoderDestroy )(void * opaque);
|
||||
typedef bool (* LG_DecoderInitialize )(void * opaque, const LG_RendererFormat format, SDL_Window * window);
|
||||
typedef void (* LG_DecoderDeInitialize )(void * opaque);
|
||||
typedef LG_OutFormat (* LG_DecoderGetOutFormat )(void * opaque);
|
||||
typedef unsigned int (* LG_DecoderGetFramePitch )(void * opaque);
|
||||
typedef unsigned int (* LG_DecoderGetFrameStride)(void * opaque);
|
||||
typedef bool (* LG_DecoderDecode )(void * opaque, const uint8_t * src, size_t srcSize);
|
||||
typedef const uint8_t * (* LG_DecoderGetBuffer )(void * opaque);
|
||||
|
||||
typedef bool (* LG_DecoderInitGLTexture )(void * opaque, GLenum target, GLuint texture, void ** ref);
|
||||
typedef void (* LG_DecoderFreeGLTexture )(void * opaque, void * ref);
|
||||
typedef bool (* LG_DecoderUpdateGLTexture)(void * opaque, void * ref);
|
||||
|
||||
typedef struct LG_Decoder
|
||||
{
|
||||
// mandatory support
|
||||
const char * name;
|
||||
LG_DecoderCreate create;
|
||||
LG_DecoderDestroy destroy;
|
||||
LG_DecoderInitialize initialize;
|
||||
LG_DecoderDeInitialize deinitialize;
|
||||
LG_DecoderGetOutFormat get_out_format;
|
||||
LG_DecoderGetFramePitch get_frame_pitch;
|
||||
LG_DecoderGetFrameStride get_frame_stride;
|
||||
LG_DecoderDecode decode;
|
||||
LG_DecoderGetBuffer get_buffer;
|
||||
|
||||
// optional support
|
||||
const bool has_gl;
|
||||
LG_DecoderInitGLTexture init_gl_texture;
|
||||
LG_DecoderFreeGLTexture free_gl_texture;
|
||||
LG_DecoderUpdateGLTexture update_gl_texture;
|
||||
}
|
||||
LG_Decoder;
|
||||
@@ -1,221 +0,0 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef _H_I_DISPLAYSERVER_
|
||||
#define _H_I_DISPLAYSERVER_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <EGL/egl.h>
|
||||
#include "common/types.h"
|
||||
|
||||
typedef enum LG_ClipboardData
|
||||
{
|
||||
LG_CLIPBOARD_DATA_TEXT = 0,
|
||||
LG_CLIPBOARD_DATA_PNG,
|
||||
LG_CLIPBOARD_DATA_BMP,
|
||||
LG_CLIPBOARD_DATA_TIFF,
|
||||
LG_CLIPBOARD_DATA_JPEG,
|
||||
|
||||
LG_CLIPBOARD_DATA_NONE // enum max, not a data type
|
||||
}
|
||||
LG_ClipboardData;
|
||||
|
||||
typedef enum LG_DSProperty
|
||||
{
|
||||
/**
|
||||
* returns the maximum number of samples supported
|
||||
* if not implemented LG assumes no multisample support
|
||||
* return data type: int
|
||||
*/
|
||||
LG_DS_MAX_MULTISAMPLE,
|
||||
|
||||
/**
|
||||
* returns if the platform is warp capable
|
||||
* if not implemented LG assumes that the platform is warp capable
|
||||
* return data type: bool
|
||||
*/
|
||||
LG_DS_WARP_SUPPORT,
|
||||
}
|
||||
LG_DSProperty;
|
||||
|
||||
enum LG_DSWarpSupport
|
||||
{
|
||||
LG_DS_WARP_NONE,
|
||||
LG_DS_WARP_SURFACE,
|
||||
LG_DS_WARP_SCREEN,
|
||||
};
|
||||
|
||||
typedef struct LG_DSInitParams
|
||||
{
|
||||
const char * title;
|
||||
int x, y, w, h;
|
||||
bool center;
|
||||
bool fullscreen;
|
||||
bool resizable;
|
||||
bool borderless;
|
||||
bool maximize;
|
||||
|
||||
// if true the renderer requires an OpenGL context
|
||||
bool opengl;
|
||||
}
|
||||
LG_DSInitParams;
|
||||
|
||||
typedef void (* LG_ClipboardReplyFn)(void * opaque, const LG_ClipboardData type,
|
||||
uint8_t * data, uint32_t size);
|
||||
|
||||
typedef struct LG_DSGLContext
|
||||
* LG_DSGLContext;
|
||||
|
||||
struct LG_DisplayServerOps
|
||||
{
|
||||
/* called before options are parsed, useful for registering options */
|
||||
void (*setup)(void);
|
||||
|
||||
/* return true if the selected ds is valid for the current platform */
|
||||
bool (*probe)(void);
|
||||
|
||||
/* called before anything has been initialized */
|
||||
bool (*earlyInit)(void);
|
||||
|
||||
/* called when it's time to create and show the application window */
|
||||
bool (*init)(const LG_DSInitParams params);
|
||||
|
||||
/* called at startup after window creation, renderer and SPICE is ready */
|
||||
void (*startup)();
|
||||
|
||||
/* called just before final window destruction, before final free */
|
||||
void (*shutdown)();
|
||||
|
||||
/* final free */
|
||||
void (*free)();
|
||||
|
||||
/*
|
||||
* return a system specific property, returns false if unsupported or failure
|
||||
* if the platform does not support/implement the requested property the value
|
||||
* of `ret` must not be altered.
|
||||
*/
|
||||
bool (*getProp)(LG_DSProperty prop, void * ret);
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
/* EGL support */
|
||||
EGLDisplay (*getEGLDisplay)(void);
|
||||
EGLNativeWindowType (*getEGLNativeWindow)(void);
|
||||
void (*eglSwapBuffers)(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
/* opengl platform specific methods */
|
||||
LG_DSGLContext (*glCreateContext)(void);
|
||||
void (*glDeleteContext)(LG_DSGLContext context);
|
||||
void (*glMakeCurrent)(LG_DSGLContext context);
|
||||
void (*glSetSwapInterval)(int interval);
|
||||
void (*glSwapBuffers)(void);
|
||||
#endif
|
||||
|
||||
/* dm specific cursor implementations */
|
||||
void (*guestPointerUpdated)(double x, double y, double localX, double localY);
|
||||
void (*showPointer)(bool show);
|
||||
void (*grabKeyboard)();
|
||||
void (*ungrabKeyboard)();
|
||||
/* (un)grabPointer is used to toggle cursor tracking/confine in normal mode */
|
||||
void (*grabPointer)();
|
||||
void (*ungrabPointer)();
|
||||
/* (un)capturePointer is used do toggle special cursor tracking in capture mode */
|
||||
void (*capturePointer)();
|
||||
void (*uncapturePointer)();
|
||||
|
||||
/* exiting = true if the warp is to leave the window */
|
||||
void (*warpPointer)(int x, int y, bool exiting);
|
||||
|
||||
/* called when the client needs to realign the pointer. This should simply
|
||||
* call the appropriate app_handleMouse* method for the platform with zero
|
||||
* deltas */
|
||||
void (*realignPointer)();
|
||||
|
||||
/* returns true if the position specified is actually valid */
|
||||
bool (*isValidPointerPos)(int x, int y);
|
||||
|
||||
/* called to disable/enable the screensaver */
|
||||
void (*inhibitIdle)();
|
||||
void (*uninhibitIdle)();
|
||||
|
||||
/* wait for the specified time without blocking UI processing/event loops */
|
||||
void (*wait)(unsigned int time);
|
||||
|
||||
/* get/set the window dimensions & state */
|
||||
void (*setWindowSize)(int x, int y);
|
||||
bool (*getFullscreen)(void);
|
||||
void (*setFullscreen)(bool fs);
|
||||
void (*minimize)(void);
|
||||
|
||||
/* clipboard support, optional, if not supported set to NULL */
|
||||
bool (*cbInit)(void);
|
||||
void (*cbNotice)(LG_ClipboardData type);
|
||||
void (*cbRelease)(void);
|
||||
void (*cbRequest)(LG_ClipboardData type);
|
||||
};
|
||||
|
||||
#ifdef ENABLE_EGL
|
||||
#define ASSERT_EGL_FN(x) assert(x);
|
||||
#else
|
||||
#define ASSERT_EGL_FN(x)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
#define ASSERT_OPENGL_FN(x) assert(x)
|
||||
#else
|
||||
#define ASSERT_OPENGL_FN(x)
|
||||
#endif
|
||||
|
||||
#define ASSERT_LG_DS_VALID(x) \
|
||||
assert((x)->setup ); \
|
||||
assert((x)->probe ); \
|
||||
assert((x)->earlyInit ); \
|
||||
assert((x)->init ); \
|
||||
assert((x)->startup ); \
|
||||
assert((x)->shutdown ); \
|
||||
assert((x)->free ); \
|
||||
assert((x)->getProp ); \
|
||||
ASSERT_EGL_FN((x)->getEGLDisplay ); \
|
||||
ASSERT_EGL_FN((x)->getEGLNativeWindow ); \
|
||||
ASSERT_EGL_FN((x)->eglSwapBuffers ); \
|
||||
ASSERT_OPENGL_FN((x)->glCreateContext ); \
|
||||
ASSERT_OPENGL_FN((x)->glDeleteContext ); \
|
||||
ASSERT_OPENGL_FN((x)->glMakeCurrent ); \
|
||||
ASSERT_OPENGL_FN((x)->glSetSwapInterval); \
|
||||
ASSERT_OPENGL_FN((x)->glSwapBuffers ); \
|
||||
assert((x)->guestPointerUpdated); \
|
||||
assert((x)->showPointer ); \
|
||||
assert((x)->grabPointer ); \
|
||||
assert((x)->ungrabPointer ); \
|
||||
assert((x)->capturePointer ); \
|
||||
assert((x)->uncapturePointer ); \
|
||||
assert((x)->warpPointer ); \
|
||||
assert((x)->realignPointer ); \
|
||||
assert((x)->isValidPointerPos ); \
|
||||
assert((x)->inhibitIdle ); \
|
||||
assert((x)->uninhibitIdle ); \
|
||||
assert((x)->wait ); \
|
||||
assert((x)->setWindowSize ); \
|
||||
assert((x)->setFullscreen ); \
|
||||
assert((x)->getFullscreen ); \
|
||||
assert((x)->minimize );
|
||||
|
||||
#endif
|
||||
@@ -1,22 +1,21 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
/*
|
||||
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
|
||||
|
||||
|
||||
@@ -1,27 +1,29 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright (C) 2017-2021 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
/*
|
||||
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 <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_ttf.h>
|
||||
|
||||
#include "app.h"
|
||||
#include "common/KVMFR.h"
|
||||
#include "common/framebuffer.h"
|
||||
@@ -31,13 +33,10 @@
|
||||
(x)->create && \
|
||||
(x)->initialize && \
|
||||
(x)->deinitialize && \
|
||||
(x)->on_restart && \
|
||||
(x)->on_resize && \
|
||||
(x)->on_mouse_shape && \
|
||||
(x)->on_mouse_event && \
|
||||
(x)->on_alert && \
|
||||
(x)->on_help && \
|
||||
(x)->on_show_fps && \
|
||||
(x)->render_startup && \
|
||||
(x)->render && \
|
||||
(x)->update_fps)
|
||||
@@ -46,48 +45,28 @@ typedef struct LG_RendererParams
|
||||
{
|
||||
// TTF_Font * font;
|
||||
// TTF_Font * alertFont;
|
||||
bool quickSplash;
|
||||
bool showFPS;
|
||||
}
|
||||
LG_RendererParams;
|
||||
|
||||
typedef enum LG_RendererSupport
|
||||
{
|
||||
LG_SUPPORTS_DMABUF
|
||||
}
|
||||
LG_RendererSupport;
|
||||
|
||||
typedef enum LG_RendererRotate
|
||||
{
|
||||
LG_ROTATE_0,
|
||||
LG_ROTATE_90,
|
||||
LG_ROTATE_180,
|
||||
LG_ROTATE_270
|
||||
}
|
||||
LG_RendererRotate;
|
||||
|
||||
// kept out of the enum so gcc doesn't warn when it's missing from a switch
|
||||
// statement.
|
||||
#define LG_ROTATE_MAX (LG_ROTATE_270+1)
|
||||
|
||||
typedef struct LG_RendererFormat
|
||||
{
|
||||
FrameType type; // frame type
|
||||
unsigned int width; // image width
|
||||
unsigned int height; // image height
|
||||
unsigned int stride; // scanline width (zero if compresed)
|
||||
unsigned int pitch; // scanline bytes (or compressed size)
|
||||
unsigned int bpp; // bits per pixel (zero if compressed)
|
||||
LG_RendererRotate rotate; // guest rotation
|
||||
FrameType type; // frame type
|
||||
unsigned int width; // image width
|
||||
unsigned int height; // image height
|
||||
unsigned int stride; // scanline width (zero if compresed)
|
||||
unsigned int pitch; // scanline bytes (or compressed size)
|
||||
unsigned int bpp; // bits per pixel (zero if compressed)
|
||||
}
|
||||
LG_RendererFormat;
|
||||
|
||||
typedef struct LG_RendererRect
|
||||
{
|
||||
bool valid;
|
||||
int x;
|
||||
int y;
|
||||
int w;
|
||||
int h;
|
||||
bool valid;
|
||||
int x;
|
||||
int y;
|
||||
unsigned int w;
|
||||
unsigned int h;
|
||||
}
|
||||
LG_RendererRect;
|
||||
|
||||
@@ -105,43 +84,32 @@ typedef const char * (* LG_RendererGetName)();
|
||||
// called pre-creation to allow the renderer to register any options it might have
|
||||
typedef void (* LG_RendererSetup)();
|
||||
|
||||
typedef bool (* LG_RendererCreate )(void ** opaque, const LG_RendererParams params, bool * needsOpenGL);
|
||||
typedef bool (* LG_RendererInitialize )(void * opaque);
|
||||
typedef void (* LG_RendererDeInitialize )(void * opaque);
|
||||
typedef bool (* LG_RendererSupports )(void * opaque, LG_RendererSupport support);
|
||||
typedef void (* LG_RendererOnRestart )(void * opaque);
|
||||
typedef void (* LG_RendererOnResize )(void * opaque, const int width, const int height, const double scale, const LG_RendererRect destRect, LG_RendererRotate rotate);
|
||||
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_RendererOnFrameFormat)(void * opaque, const LG_RendererFormat format, bool useDMA);
|
||||
typedef bool (* LG_RendererOnFrame )(void * opaque, const FrameBuffer * frame, int dmaFD);
|
||||
typedef void (* LG_RendererOnAlert )(void * opaque, const LG_MsgAlert alert, const char * message, bool ** closeFlag);
|
||||
typedef void (* LG_RendererOnHelp )(void * opaque, const char * message);
|
||||
typedef void (* LG_RendererOnShowFPS )(void * opaque, bool showFPS);
|
||||
typedef bool (* LG_RendererRenderStartup)(void * opaque);
|
||||
typedef bool (* LG_RendererRender )(void * opaque, LG_RendererRotate rotate);
|
||||
typedef void (* LG_RendererUpdateFPS )(void * opaque, const float avgUPS, const float avgFPS);
|
||||
typedef bool (* LG_RendererCreate )(void ** opaque, const LG_RendererParams params);
|
||||
typedef bool (* LG_RendererInitialize )(void * opaque, Uint32 * sdlFlags);
|
||||
typedef void (* LG_RendererDeInitialize)(void * opaque);
|
||||
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_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 void (* LG_RendererOnAlert )(void * opaque, const LG_MsgAlert alert, const char * message, bool ** closeFlag);
|
||||
typedef bool (* LG_RendererRender )(void * opaque, SDL_Window *window);
|
||||
typedef void (* LG_RendererUpdateFPS )(void * opaque, const float avgUPS, const float avgFPS);
|
||||
|
||||
typedef struct LG_Renderer
|
||||
{
|
||||
LG_RendererGetName get_name;
|
||||
LG_RendererSetup setup;
|
||||
|
||||
LG_RendererCreate create;
|
||||
LG_RendererInitialize initialize;
|
||||
LG_RendererDeInitialize deinitialize;
|
||||
LG_RendererSupports supports;
|
||||
LG_RendererOnRestart on_restart;
|
||||
LG_RendererOnResize on_resize;
|
||||
LG_RendererOnMouseShape on_mouse_shape;
|
||||
LG_RendererOnMouseEvent on_mouse_event;
|
||||
LG_RendererOnFrameFormat on_frame_format;
|
||||
LG_RendererOnFrame on_frame;
|
||||
LG_RendererOnAlert on_alert;
|
||||
LG_RendererOnHelp on_help;
|
||||
LG_RendererOnShowFPS on_show_fps;
|
||||
LG_RendererRenderStartup render_startup;
|
||||
LG_RendererRender render;
|
||||
LG_RendererUpdateFPS update_fps;
|
||||
LG_RendererCreate create;
|
||||
LG_RendererInitialize initialize;
|
||||
LG_RendererDeInitialize deinitialize;
|
||||
LG_RendererOnResize on_resize;
|
||||
LG_RendererOnMouseShape on_mouse_shape;
|
||||
LG_RendererOnMouseEvent on_mouse_event;
|
||||
LG_RendererOnFrameEvent on_frame_event;
|
||||
LG_RendererOnAlert on_alert;
|
||||
LG_RendererRender render_startup;
|
||||
LG_RendererRender render;
|
||||
LG_RendererUpdateFPS update_fps;
|
||||
}
|
||||
LG_Renderer;
|
||||
LG_Renderer;
|
||||
33
client/include/lg-decoders.h
Normal file
33
client/include/lg-decoders.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
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 "interface/decoder.h"
|
||||
|
||||
extern const LG_Decoder LGD_NULL;
|
||||
extern const LG_Decoder LGD_YUV420;
|
||||
|
||||
const LG_Decoder * LG_Decoders[] =
|
||||
{
|
||||
&LGD_NULL,
|
||||
&LGD_YUV420,
|
||||
NULL // end of array sentinal
|
||||
};
|
||||
|
||||
#define LG_DECODER_COUNT ((sizeof(LG_Decoders) / sizeof(LG_Decoder *)) - 1)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user