Compare commits

..

28 Commits

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

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

12
.github/FUNDING.yml vendored
View File

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

View File

@@ -1,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**

View File

@@ -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)

View File

@@ -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
View File

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

6
.gitmodules vendored
View File

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

55
AUTHORS
View File

@@ -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)

View File

@@ -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
VERSION Normal file
View File

@@ -0,0 +1 @@
B1-43-g83047cbc3e+1

1
_config.yml Normal file
View File

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

View File

@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.0)
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}/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
View File

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

View File

@@ -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(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
View 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_

View 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;

View File

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

View File

@@ -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

View File

@@ -9,7 +9,7 @@ target_link_libraries(capture_XCB
lg_common
xcb
xcb-shm
xcb-xfixes
Xfixes
)
target_include_directories(capture_XCB

View File

@@ -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
};

View File

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

View File

@@ -7,14 +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
)
# 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")
@@ -28,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)

View File

@@ -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>

View File

@@ -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})

View File

@@ -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>

View 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
};

View File

@@ -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;

View File

@@ -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>

View 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

View 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();

View 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);

View 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);
}

View File

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

View File

@@ -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);

View 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
View File

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

3
client/.gitignore vendored Normal file
View File

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

10
client/.vimrc Normal file
View File

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

View File

@@ -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
View 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 |
|------------------------------------------------------------------------------------|
```

View 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})

View 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}
)

View 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
};

View File

@@ -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()

View 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
View 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
View 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
};

View 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
};

View File

@@ -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})

View File

@@ -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}
)

View File

@@ -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,
};

View File

@@ -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,
};

View File

@@ -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")

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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, &registryListener, NULL);
wl_display_roundtrip(wlWm.display);
return true;
}
void waylandRegistryFree(void)
{
wl_registry_destroy(wlWm.registry);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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
};

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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}
)

View File

@@ -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
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View 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
View 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
};

View File

@@ -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}
)

View File

@@ -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
};

View File

@@ -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

View File

@@ -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_

View 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);

View 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;

View 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;

View File

@@ -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

View File

@@ -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

View File

@@ -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,29 +45,10 @@ 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
@@ -77,7 +57,6 @@ typedef struct LG_RendererFormat
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
}
LG_RendererFormat;
@@ -86,8 +65,8 @@ typedef struct LG_RendererRect
bool valid;
int x;
int y;
int w;
int h;
unsigned int w;
unsigned int h;
}
LG_RendererRect;
@@ -105,21 +84,15 @@ 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 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 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 bool (* LG_RendererRender )(void * opaque, SDL_Window *window);
typedef void (* LG_RendererUpdateFPS )(void * opaque, const float avgUPS, const float avgFPS);
typedef struct LG_Renderer
@@ -130,17 +103,12 @@ typedef struct LG_Renderer
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_RendererOnFrameEvent on_frame_event;
LG_RendererOnAlert on_alert;
LG_RendererOnHelp on_help;
LG_RendererOnShowFPS on_show_fps;
LG_RendererRenderStartup render_startup;
LG_RendererRender render_startup;
LG_RendererRender render;
LG_RendererUpdateFPS update_fps;
}

View 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