mirror of
https://github.com/gnif/LookingGlass.git
synced 2025-12-19 19:20:16 +00:00
Compare commits
324 Commits
Release/B5
...
host-downs
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
655c993c5b | ||
|
|
7b7a06b63f | ||
|
|
81f91caf0e | ||
|
|
3d727a2254 | ||
|
|
e4a4e2331a | ||
|
|
8682ec207e | ||
|
|
c8a5293645 | ||
|
|
15334c89d6 | ||
|
|
2eec459b47 | ||
|
|
947325e00d | ||
|
|
eae559b4c9 | ||
|
|
3134ec84de | ||
|
|
132d0e3c42 | ||
|
|
4bbdd30284 | ||
|
|
6d06320fb2 | ||
|
|
f3fe774f69 | ||
|
|
e053c014f7 | ||
|
|
9c8a8a1b44 | ||
|
|
1685249f3a | ||
|
|
97cef000fd | ||
|
|
8f45290beb | ||
|
|
9afe170413 | ||
|
|
dd6d9c44df | ||
|
|
fb5a71c47e | ||
|
|
75370e464d | ||
|
|
c55d0a82f2 | ||
|
|
f28084e653 | ||
|
|
6c76d6ada5 | ||
|
|
3a8cb6a613 | ||
|
|
a3820536ab | ||
|
|
36f97f08ad | ||
|
|
48cf099638 | ||
|
|
88d60d4b3d | ||
|
|
3189c7bcd6 | ||
|
|
d1da0d62ed | ||
|
|
72033f3822 | ||
|
|
c2523be4b4 | ||
|
|
7efc274e81 | ||
|
|
7c2d493bb5 | ||
|
|
9908b737b0 | ||
|
|
0dad9b1e76 | ||
|
|
a13c90bd27 | ||
|
|
84b5478b02 | ||
|
|
38340d3497 | ||
|
|
eeefc15e46 | ||
|
|
91d6e3a82a | ||
|
|
7387a4a8e1 | ||
|
|
d9dc399522 | ||
|
|
70158a64e7 | ||
|
|
1ca43c4727 | ||
|
|
fc96b6691e | ||
|
|
5a94f82f10 | ||
|
|
5b7c38a4dd | ||
|
|
f01489720f | ||
|
|
202116786c | ||
|
|
8b4551c39c | ||
|
|
29698362ed | ||
|
|
f24db8d0cd | ||
|
|
cfd2e6ff32 | ||
|
|
e96311eb7b | ||
|
|
0d97a51802 | ||
|
|
5e1b8f2abe | ||
|
|
e0c0451b52 | ||
|
|
9ddfa585ec | ||
|
|
0ea188faf8 | ||
|
|
e1ac838796 | ||
|
|
770a4279ee | ||
|
|
1cfbcba813 | ||
|
|
3890c72159 | ||
|
|
4223a5e38f | ||
|
|
809e1095bd | ||
|
|
fd28d0604e | ||
|
|
30c57f411d | ||
|
|
9cd8027901 | ||
|
|
969ac4d1d1 | ||
|
|
da548e3858 | ||
|
|
21a349343b | ||
|
|
4ee6bdf198 | ||
|
|
b13582a911 | ||
|
|
05ca59ed48 | ||
|
|
e1e60fdaa6 | ||
|
|
ca29fe80a6 | ||
|
|
35bf30910b | ||
|
|
db78c8e468 | ||
|
|
febd081202 | ||
|
|
5bbc1d44bf | ||
|
|
22b968ff53 | ||
|
|
a0477466d2 | ||
|
|
c2a766c2ee | ||
|
|
4ff39616b2 | ||
|
|
2201ed869e | ||
|
|
d0b3c09456 | ||
|
|
a560a610d9 | ||
|
|
a7db3d3a0f | ||
|
|
016001da67 | ||
|
|
41884bfcc5 | ||
|
|
dd2d84a080 | ||
|
|
599fdd6ffd | ||
|
|
b34b253814 | ||
|
|
68b42e1c1a | ||
|
|
8580978321 | ||
|
|
d93510e9f2 | ||
|
|
75ec3c0478 | ||
|
|
e85fd68d82 | ||
|
|
f247d7f0da | ||
|
|
b0568ca404 | ||
|
|
3c9b9e6370 | ||
|
|
db3d20f935 | ||
|
|
ccdf7b7c0e | ||
|
|
efa49391fc | ||
|
|
fb4bdaee2b | ||
|
|
c7389285f9 | ||
|
|
aa426d13a7 | ||
|
|
89c83dafc1 | ||
|
|
05e363e009 | ||
|
|
e17b289759 | ||
|
|
79e986cc60 | ||
|
|
22f3cf5ba6 | ||
|
|
3067bdaa15 | ||
|
|
f3ebde7d9f | ||
|
|
905c1d7f58 | ||
|
|
11800029f0 | ||
|
|
71901414d1 | ||
|
|
96fa8891c8 | ||
|
|
1082875b8e | ||
|
|
dc918c55b6 | ||
|
|
a8ba014b52 | ||
|
|
9a6aa3ce66 | ||
|
|
f2fbb2b27c | ||
|
|
829db8a0e4 | ||
|
|
9601bc677f | ||
|
|
aba30e9541 | ||
|
|
c84879717f | ||
|
|
b3c81bcedf | ||
|
|
7f4dcd1ced | ||
|
|
15f76339c8 | ||
|
|
99536eaf9d | ||
|
|
f8b4874799 | ||
|
|
cff64ee7d3 | ||
|
|
04ae9217e8 | ||
|
|
46da447429 | ||
|
|
4b080f7610 | ||
|
|
d6bbc4f89c | ||
|
|
4fadf3a130 | ||
|
|
73dc08e5f9 | ||
|
|
07c92ec2e8 | ||
|
|
b334f22223 | ||
|
|
aad65c1cab | ||
|
|
0ad26b7da7 | ||
|
|
775ac7ce8b | ||
|
|
689cc53255 | ||
|
|
5629655f74 | ||
|
|
54e7542414 | ||
|
|
464fee3e20 | ||
|
|
42ed0d7638 | ||
|
|
5a3fe151e4 | ||
|
|
afd5e2d057 | ||
|
|
508c491967 | ||
|
|
b117bbafe5 | ||
|
|
5392f815af | ||
|
|
4c271f8744 | ||
|
|
745169fae2 | ||
|
|
7f79352320 | ||
|
|
b020372972 | ||
|
|
5fe529f213 | ||
|
|
7c91c922e6 | ||
|
|
9c49dc6efd | ||
|
|
f635077a2c | ||
|
|
a9b5302a51 | ||
|
|
3d0a8f6987 | ||
|
|
786a252b23 | ||
|
|
f145225dbc | ||
|
|
b38a5ce89e | ||
|
|
344d2ec599 | ||
|
|
6bba9bc25d | ||
|
|
1851002fc1 | ||
|
|
b99e1ea38e | ||
|
|
2ecfa0a3ec | ||
|
|
ca0bc7c514 | ||
|
|
4122841b09 | ||
|
|
e94252ad65 | ||
|
|
6fc0c69b2e | ||
|
|
ced952a4c6 | ||
|
|
4411d21135 | ||
|
|
70683010a6 | ||
|
|
7da2becfbd | ||
|
|
8a61c8ebc2 | ||
|
|
ef9b2958ec | ||
|
|
e72e138267 | ||
|
|
4c389a9274 | ||
|
|
b9c646074d | ||
|
|
042a7d0925 | ||
|
|
c69b19e68f | ||
|
|
cf7d501bc4 | ||
|
|
68e5b812a9 | ||
|
|
5a93f1e00c | ||
|
|
891f00a011 | ||
|
|
137171a8a2 | ||
|
|
36892839f3 | ||
|
|
0fc87576f3 | ||
|
|
3ffefb5281 | ||
|
|
fd12d9901a | ||
|
|
c05282c38c | ||
|
|
a391e271c3 | ||
|
|
24193aaaa6 | ||
|
|
f9b907a6b1 | ||
|
|
b8866a2ce4 | ||
|
|
d42e409728 | ||
|
|
780cf5f362 | ||
|
|
0080e5f1b9 | ||
|
|
ad6fa5a504 | ||
|
|
db2e38ae4d | ||
|
|
35334333ac | ||
|
|
ec0bd6adc8 | ||
|
|
8e8d8834de | ||
|
|
bf059a6eda | ||
|
|
2834c7d95b | ||
|
|
2099161b7e | ||
|
|
a40a964b30 | ||
|
|
194241c5a3 | ||
|
|
32134b33ea | ||
|
|
9d894065c8 | ||
|
|
62c5d68fc6 | ||
|
|
0f998582b9 | ||
|
|
7263159428 | ||
|
|
52f06ec332 | ||
|
|
7f93bbd675 | ||
|
|
5c20a851c6 | ||
|
|
11acaa2957 | ||
|
|
fe7973ea24 | ||
|
|
ff2ca20235 | ||
|
|
a114ea3de4 | ||
|
|
e6bd36ec7c | ||
|
|
34e5f7e968 | ||
|
|
2f8b139131 | ||
|
|
b058cbe9fe | ||
|
|
443f98d2fa | ||
|
|
92f27cc0f0 | ||
|
|
208b722348 | ||
|
|
67509d7a2d | ||
|
|
c20bb27b67 | ||
|
|
8cdeaceed9 | ||
|
|
7bcd0dd97f | ||
|
|
5bb1f01dea | ||
|
|
297d0be2dc | ||
|
|
fdb38a227e | ||
|
|
7ccd202d36 | ||
|
|
177a997883 | ||
|
|
b3f6c75ade | ||
|
|
912ca62a7b | ||
|
|
952ebea2c5 | ||
|
|
0d27092ef5 | ||
|
|
ebf20dd108 | ||
|
|
7cc9b5f77c | ||
|
|
0ccc84959e | ||
|
|
ba9f2b85b6 | ||
|
|
ed61a7adf9 | ||
|
|
d708651c53 | ||
|
|
0d00936aac | ||
|
|
6347f02efe | ||
|
|
dfdc407bc6 | ||
|
|
ac2c62e560 | ||
|
|
4b8255aa28 | ||
|
|
b6fedf1420 | ||
|
|
c8b4787cb1 | ||
|
|
d43126f433 | ||
|
|
6f39434bdc | ||
|
|
9b202d5566 | ||
|
|
764e52fb20 | ||
|
|
6f17e89b16 | ||
|
|
d8e7a83226 | ||
|
|
c74d48691f | ||
|
|
7c8f42855d | ||
|
|
d1a765c179 | ||
|
|
2ed3c82de0 | ||
|
|
17b77cfbc1 | ||
|
|
65ba2e8df9 | ||
|
|
e7fdf7e77a | ||
|
|
aa5922a1b4 | ||
|
|
142902b7b3 | ||
|
|
10110dd940 | ||
|
|
192fb1cdc7 | ||
|
|
35efa551ef | ||
|
|
f53adc7a05 | ||
|
|
a21e897bb5 | ||
|
|
136737f25b | ||
|
|
95987a9c91 | ||
|
|
bbd9c84896 | ||
|
|
8ab130deba | ||
|
|
fbf294efd9 | ||
|
|
2824238b4d | ||
|
|
bb74a9d9c8 | ||
|
|
9ff476bd09 | ||
|
|
6ef3fea05e | ||
|
|
02ec25b008 | ||
|
|
4e75c576b2 | ||
|
|
90dd1f3913 | ||
|
|
a8ddf72318 | ||
|
|
5d9db8b2f5 | ||
|
|
672cd246ab | ||
|
|
936688ddac | ||
|
|
ff6c46f7ca | ||
|
|
4dccd725bf | ||
|
|
6f8745a89b | ||
|
|
f971a01801 | ||
|
|
3d1eedd4ef | ||
|
|
d073f9969c | ||
|
|
b21d842f0e | ||
|
|
9fa643484c | ||
|
|
64b64b61be | ||
|
|
433a5420cb | ||
|
|
e408ea51e2 | ||
|
|
cca6492069 | ||
|
|
141d5d3731 | ||
|
|
ebdc847ef1 | ||
|
|
2ea24516d2 | ||
|
|
dd04a46403 | ||
|
|
d99ec3e9c0 | ||
|
|
f403033ab1 | ||
|
|
11ef94c134 | ||
|
|
75e46128d4 | ||
|
|
e810577317 | ||
|
|
8ba4b56dba | ||
|
|
d69069fb09 |
23
.github/workflows/build.yml
vendored
23
.github/workflows/build.yml
vendored
@@ -18,6 +18,9 @@ jobs:
|
||||
- name: Install libdecor PPA
|
||||
run: sudo add-apt-repository ppa:christianrauch/libdecoration
|
||||
if: ${{ matrix.wayland_shell == 'libdecor' }}
|
||||
- name: Install PipeWire repository
|
||||
run: |
|
||||
echo 'deb [trusted=yes] https://pipewire-ubuntu.quantum5.workers.dev ./' | sudo tee /etc/apt/sources.list.d/pipewire.list
|
||||
- name: Update apt
|
||||
run: |
|
||||
sudo apt-get update
|
||||
@@ -28,8 +31,9 @@ jobs:
|
||||
libspice-protocol-dev nettle-dev \
|
||||
libgl-dev libgles-dev \
|
||||
libx11-dev libxss-dev libxi-dev libxinerama-dev libxcursor-dev libxpresent-dev \
|
||||
libwayland-dev wayland-protocols libxkbcommon-dev \
|
||||
$([ '${{ matrix.wayland_shell }}' = libdecor ] && echo 'libdecor-dev libdbus-1-dev') \
|
||||
libwayland-dev libxkbcommon-dev \
|
||||
libsamplerate0-dev libpipewire-0.3-dev libpulse-dev \
|
||||
$([ '${{ matrix.wayland_shell }}' = libdecor ] && echo 'libdecor-0-dev libdbus-1-dev') \
|
||||
$([ '${{ matrix.compiler.cc }}' = clang ] && echo 'clang-tools')
|
||||
sudo pip3 install pyenchant
|
||||
- name: Configure client
|
||||
@@ -43,7 +47,7 @@ jobs:
|
||||
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \
|
||||
-DCMAKE_LINKER:FILEPATH=/usr/bin/ld \
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||
-DENABLE_LIBDECOR=${{ matrix.wayland_shell == 'libdecor' }} \
|
||||
-DENABLE_LIBDECOR=${{ matrix.wayland_shell == 'libdecor' }} \
|
||||
..
|
||||
- name: Build client
|
||||
run: |
|
||||
@@ -72,12 +76,16 @@ jobs:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install PipeWire repository
|
||||
run: |
|
||||
echo 'deb [trusted=yes] https://pipewire-ubuntu.quantum5.workers.dev ./' | sudo tee /etc/apt/sources.list.d/pipewire.list
|
||||
- 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
|
||||
sudo apt-get install binutils-dev libxcb-xfixes0-dev \
|
||||
libpipewire-0.3-dev
|
||||
- name: Configure Linux host
|
||||
run: |
|
||||
mkdir host/build
|
||||
@@ -113,6 +121,11 @@ jobs:
|
||||
run: |
|
||||
cd host/build
|
||||
makensis platform/Windows/installer.nsi
|
||||
- name: Build Windows host installer with IVSHMEM drivers
|
||||
run: |
|
||||
cd host/build
|
||||
curl https://dl.quantum2.xyz/ivshmem.tar.gz | tar xz
|
||||
makensis -DIVSHMEM platform/Windows/installer.nsi
|
||||
|
||||
host-windows-native:
|
||||
runs-on: windows-latest
|
||||
@@ -170,7 +183,7 @@ jobs:
|
||||
sudo apt-get update
|
||||
- name: Install docs dependencies
|
||||
run: |
|
||||
sudo apt-get install python3-sphinx
|
||||
sudo apt-get install python3-sphinx python3-sphinx-rtd-theme
|
||||
sudo pip3 install sphinxcontrib-spelling
|
||||
- name: Build docs
|
||||
run: |
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -7,3 +7,6 @@
|
||||
[submodule "repos/cimgui"]
|
||||
path = repos/cimgui
|
||||
url = https://github.com/cimgui/cimgui.git
|
||||
[submodule "repos/wayland-protocols"]
|
||||
path = repos/wayland-protocols
|
||||
url = https://gitlab.freedesktop.org/wayland/wayland-protocols.git
|
||||
|
||||
6
AUTHORS
6
AUTHORS
@@ -54,3 +54,9 @@ thejavascriptman <thejavascriptman@outlook.com> (thejavascriptman)
|
||||
vroad <396351+vroad@users.noreply.github.com> (vroad)
|
||||
williamvds <w.vigolodasilva@gmail.com> (williamvds)
|
||||
SytheZN <sythe.zn@gmail.com> (SytheZN)
|
||||
RTXUX <wyf@rtxux.xyz> (RTXUX)
|
||||
Vincent LaRocca <vincentmlarocca@gmail.com> (VMFortress)
|
||||
Johnathon Paul Weaver <weaver123_johnathon@hotmail.com> (8BallBomBom)
|
||||
Chris Spencer <spencercw@gmail.com> (spencercw)
|
||||
Mark Boorer <markboo99@gmail.com> (Shootfast)
|
||||
babbaj <babbaj45@gmail.com> (Babbaj)
|
||||
|
||||
@@ -19,4 +19,4 @@ https://looking-glass.io/downloads
|
||||
Source code for the documentation can be found in the `/doc` directory.
|
||||
|
||||
You may view this locally as HTML by running `make html` with `python3-sphinx`
|
||||
installed.
|
||||
and `python3-sphinx-rtd-theme` installed.
|
||||
|
||||
@@ -51,6 +51,12 @@ 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.")
|
||||
|
||||
option(ENABLE_PIPEWIRE "Build with PipeWire audio output support" ON)
|
||||
add_feature_info(ENABLE_PIPEWIRE ENABLE_PIPEWIRE "PipeWire audio support.")
|
||||
|
||||
option(ENABLE_PULSEAUDIO "Build with PulseAudio audio output support" ON)
|
||||
add_feature_info(ENABLE_PULSEAUDIO ENABLE_PULSEAUDIO "PulseAudio audio support.")
|
||||
|
||||
if (NOT ENABLE_X11 AND NOT ENABLE_WAYLAND)
|
||||
message(FATAL_ERROR "Either ENABLE_X11 or ENABLE_WAYLAND must be on")
|
||||
endif()
|
||||
@@ -60,6 +66,7 @@ add_compile_options(
|
||||
"-Wextra"
|
||||
"-Wno-sign-compare"
|
||||
"-Wno-unused-parameter"
|
||||
"-Wstrict-prototypes"
|
||||
"$<$<C_COMPILER_ID:GNU>:-Wimplicit-fallthrough=2>"
|
||||
"-Werror"
|
||||
"-Wfatal-errors"
|
||||
@@ -101,6 +108,7 @@ add_custom_command(
|
||||
)
|
||||
|
||||
include_directories(
|
||||
${PROJECT_TOP}
|
||||
${PROJECT_SOURCE_DIR}/include
|
||||
${CMAKE_BINARY_DIR}/include
|
||||
)
|
||||
@@ -116,9 +124,9 @@ set(SOURCES
|
||||
src/main.c
|
||||
src/core.c
|
||||
src/app.c
|
||||
src/audio.c
|
||||
src/config.c
|
||||
src/keybind.c
|
||||
src/ll.c
|
||||
src/util.c
|
||||
src/clipboard.c
|
||||
src/kb.c
|
||||
@@ -132,6 +140,8 @@ set(SOURCES
|
||||
src/overlay/graphs.c
|
||||
src/overlay/help.c
|
||||
src/overlay/config.c
|
||||
src/overlay/msg.c
|
||||
src/overlay/record.c
|
||||
)
|
||||
|
||||
# Force cimgui to build as a static library.
|
||||
@@ -160,6 +170,16 @@ target_link_libraries(looking-glass-client
|
||||
cimgui
|
||||
)
|
||||
|
||||
if (ENABLE_PIPEWIRE OR ENABLE_PULSEAUDIO)
|
||||
add_definitions(-D ENABLE_AUDIO)
|
||||
add_subdirectory(audiodevs)
|
||||
pkg_check_modules(SAMPLERATE REQUIRED IMPORTED_TARGET samplerate)
|
||||
target_link_libraries(looking-glass-client
|
||||
PkgConfig::SAMPLERATE
|
||||
audiodevs
|
||||
)
|
||||
endif()
|
||||
|
||||
install(TARGETS looking-glass-client
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
COMPONENT binary)
|
||||
|
||||
46
client/audiodevs/CMakeLists.txt
Normal file
46
client/audiodevs/CMakeLists.txt
Normal file
@@ -0,0 +1,46 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(audiodevs LANGUAGES C)
|
||||
|
||||
set(AUDIODEV_H "${CMAKE_BINARY_DIR}/include/dynamic/audiodev.h")
|
||||
set(AUDIODEV_C "${CMAKE_BINARY_DIR}/src/audiodev.c")
|
||||
|
||||
file(WRITE ${AUDIODEV_H} "#include \"interface/audiodev.h\"\n\n")
|
||||
file(APPEND ${AUDIODEV_H} "extern struct LG_AudioDevOps * LG_AudioDevs[];\n\n")
|
||||
|
||||
file(WRITE ${AUDIODEV_C} "#include \"interface/audiodev.h\"\n\n")
|
||||
file(APPEND ${AUDIODEV_C} "#include <stddef.h>\n\n")
|
||||
|
||||
set(AUDIODEVS "_")
|
||||
set(AUDIODEVS_LINK "_")
|
||||
function(add_audiodev name)
|
||||
set(AUDIODEVS "${AUDIODEVS};${name}" PARENT_SCOPE)
|
||||
set(AUDIODEVS_LINK "${AUDIODEVS_LINK};audiodev_${name}" PARENT_SCOPE)
|
||||
add_subdirectory(${name})
|
||||
endfunction()
|
||||
|
||||
# Add/remove audiodevs here!
|
||||
if(ENABLE_PIPEWIRE)
|
||||
add_audiodev(PipeWire)
|
||||
endif()
|
||||
if(ENABLE_PULSEAUDIO)
|
||||
add_audiodev(PulseAudio)
|
||||
endif()
|
||||
|
||||
list(REMOVE_AT AUDIODEVS 0)
|
||||
list(REMOVE_AT AUDIODEVS_LINK 0)
|
||||
|
||||
list(LENGTH AUDIODEVS AUDIODEV_COUNT)
|
||||
file(APPEND ${AUDIODEV_H} "#define LG_AUDIODEV_COUNT ${AUDIODEV_COUNT}\n")
|
||||
|
||||
foreach(audiodev ${AUDIODEVS})
|
||||
file(APPEND ${AUDIODEV_C} "extern struct LG_AudioDevOps LGAD_${audiodev};\n")
|
||||
endforeach()
|
||||
|
||||
file(APPEND ${AUDIODEV_C} "\nconst struct LG_AudioDevOps * LG_AudioDevs[] =\n{\n")
|
||||
foreach(audiodev ${AUDIODEVS})
|
||||
file(APPEND ${AUDIODEV_C} " &LGAD_${audiodev},\n")
|
||||
endforeach()
|
||||
file(APPEND ${AUDIODEV_C} " NULL\n};")
|
||||
|
||||
add_library(audiodevs STATIC ${AUDIODEV_C})
|
||||
target_link_libraries(audiodevs ${AUDIODEVS_LINK})
|
||||
21
client/audiodevs/PipeWire/CMakeLists.txt
Normal file
21
client/audiodevs/PipeWire/CMakeLists.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(audiodev_PipeWire LANGUAGES C)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(AUDIODEV_PipeWire REQUIRED IMPORTED_TARGET
|
||||
libpipewire-0.3
|
||||
)
|
||||
|
||||
add_library(audiodev_PipeWire STATIC
|
||||
pipewire.c
|
||||
)
|
||||
|
||||
target_link_libraries(audiodev_PipeWire
|
||||
PkgConfig::AUDIODEV_PipeWire
|
||||
lg_common
|
||||
)
|
||||
|
||||
target_include_directories(audiodev_PipeWire
|
||||
PRIVATE
|
||||
src
|
||||
)
|
||||
565
client/audiodevs/PipeWire/pipewire.c
Normal file
565
client/audiodevs/PipeWire/pipewire.c
Normal file
@@ -0,0 +1,565 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2022 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/audiodev.h"
|
||||
|
||||
#include <spa/param/audio/format-utils.h>
|
||||
#include <spa/param/props.h>
|
||||
#include <pipewire/pipewire.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/stringutils.h"
|
||||
#include "common/util.h"
|
||||
|
||||
typedef enum
|
||||
{
|
||||
STREAM_STATE_INACTIVE,
|
||||
STREAM_STATE_ACTIVE,
|
||||
STREAM_STATE_DRAINING
|
||||
}
|
||||
StreamState;
|
||||
|
||||
struct PipeWire
|
||||
{
|
||||
struct pw_loop * loop;
|
||||
struct pw_context * context;
|
||||
struct pw_thread_loop * thread;
|
||||
|
||||
struct
|
||||
{
|
||||
struct pw_stream * stream;
|
||||
struct spa_io_rate_match * rateMatch;
|
||||
|
||||
int channels;
|
||||
int sampleRate;
|
||||
int stride;
|
||||
LG_AudioPullFn pullFn;
|
||||
int maxPeriodFrames;
|
||||
int startFrames;
|
||||
|
||||
StreamState state;
|
||||
}
|
||||
playback;
|
||||
|
||||
struct
|
||||
{
|
||||
struct pw_stream * stream;
|
||||
|
||||
int channels;
|
||||
int sampleRate;
|
||||
int stride;
|
||||
LG_AudioPushFn pushFn;
|
||||
|
||||
bool active;
|
||||
}
|
||||
record;
|
||||
};
|
||||
|
||||
static struct PipeWire pw = {0};
|
||||
|
||||
static void pipewire_onPlaybackIoChanged(void * userdata, uint32_t id,
|
||||
void * data, uint32_t size)
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case SPA_IO_RateMatch:
|
||||
pw.playback.rateMatch = data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void pipewire_onPlaybackProcess(void * userdata)
|
||||
{
|
||||
struct pw_buffer * pbuf;
|
||||
|
||||
if (!(pbuf = pw_stream_dequeue_buffer(pw.playback.stream)))
|
||||
{
|
||||
DEBUG_WARN("out of buffers");
|
||||
return;
|
||||
}
|
||||
|
||||
struct spa_buffer * sbuf = pbuf->buffer;
|
||||
uint8_t * dst;
|
||||
|
||||
if (!(dst = sbuf->datas[0].data))
|
||||
return;
|
||||
|
||||
int frames = sbuf->datas[0].maxsize / pw.playback.stride;
|
||||
if (pw.playback.rateMatch && pw.playback.rateMatch->size > 0)
|
||||
frames = min(frames, pw.playback.rateMatch->size);
|
||||
|
||||
frames = pw.playback.pullFn(dst, frames);
|
||||
if (!frames)
|
||||
{
|
||||
sbuf->datas[0].chunk->size = 0;
|
||||
pw_stream_queue_buffer(pw.playback.stream, pbuf);
|
||||
return;
|
||||
}
|
||||
|
||||
sbuf->datas[0].chunk->offset = 0;
|
||||
sbuf->datas[0].chunk->stride = pw.playback.stride;
|
||||
sbuf->datas[0].chunk->size = frames * pw.playback.stride;
|
||||
|
||||
pw_stream_queue_buffer(pw.playback.stream, pbuf);
|
||||
}
|
||||
|
||||
static void pipewire_onPlaybackDrained(void * userdata)
|
||||
{
|
||||
pw_thread_loop_lock(pw.thread);
|
||||
pw_stream_set_active(pw.playback.stream, false);
|
||||
pw.playback.state = STREAM_STATE_INACTIVE;
|
||||
pw_thread_loop_unlock(pw.thread);
|
||||
}
|
||||
|
||||
static bool pipewire_init(void)
|
||||
{
|
||||
pw_init(NULL, NULL);
|
||||
|
||||
pw.loop = pw_loop_new(NULL);
|
||||
pw.context = pw_context_new(
|
||||
pw.loop,
|
||||
pw_properties_new(
|
||||
// Request real-time priority on the PipeWire threads
|
||||
PW_KEY_CONFIG_NAME, "client-rt.conf",
|
||||
NULL
|
||||
),
|
||||
0);
|
||||
if (!pw.context)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create a context");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* this is just to test for PipeWire availabillity */
|
||||
struct pw_core * core = pw_context_connect(pw.context, NULL, 0);
|
||||
if (!core)
|
||||
goto err_context;
|
||||
|
||||
/* PipeWire is available so create the loop thread and start it */
|
||||
pw.thread = pw_thread_loop_new_full(pw.loop, "PipeWire", NULL);
|
||||
if (!pw.thread)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the thread loop");
|
||||
goto err_context;
|
||||
}
|
||||
|
||||
pw_thread_loop_start(pw.thread);
|
||||
return true;
|
||||
|
||||
err_context:
|
||||
pw_context_destroy(pw.context);
|
||||
|
||||
err:
|
||||
pw_loop_destroy(pw.loop);
|
||||
pw_deinit();
|
||||
return false;
|
||||
}
|
||||
|
||||
static void pipewire_playbackStopStream(void)
|
||||
{
|
||||
if (!pw.playback.stream)
|
||||
return;
|
||||
|
||||
pw_thread_loop_lock(pw.thread);
|
||||
pw_stream_destroy(pw.playback.stream);
|
||||
pw.playback.stream = NULL;
|
||||
pw.playback.rateMatch = NULL;
|
||||
pw_thread_loop_unlock(pw.thread);
|
||||
}
|
||||
|
||||
static void pipewire_playbackSetup(int channels, int sampleRate,
|
||||
int requestedPeriodFrames, int * maxPeriodFrames, int * startFrames,
|
||||
LG_AudioPullFn pullFn)
|
||||
{
|
||||
const struct spa_pod * params[1];
|
||||
uint8_t buffer[1024];
|
||||
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
|
||||
static const struct pw_stream_events events =
|
||||
{
|
||||
.version = PW_VERSION_STREAM_EVENTS,
|
||||
.io_changed = pipewire_onPlaybackIoChanged,
|
||||
.process = pipewire_onPlaybackProcess,
|
||||
.drained = pipewire_onPlaybackDrained
|
||||
};
|
||||
|
||||
if (pw.playback.stream &&
|
||||
pw.playback.channels == channels &&
|
||||
pw.playback.sampleRate == sampleRate)
|
||||
{
|
||||
*maxPeriodFrames = pw.playback.maxPeriodFrames;
|
||||
*startFrames = pw.playback.startFrames;
|
||||
return;
|
||||
}
|
||||
|
||||
pipewire_playbackStopStream();
|
||||
|
||||
char requestedNodeLatency[32];
|
||||
snprintf(requestedNodeLatency, sizeof(requestedNodeLatency), "%d/%d",
|
||||
requestedPeriodFrames, sampleRate);
|
||||
|
||||
pw.playback.channels = channels;
|
||||
pw.playback.sampleRate = sampleRate;
|
||||
pw.playback.stride = sizeof(float) * channels;
|
||||
pw.playback.pullFn = pullFn;
|
||||
|
||||
pw_thread_loop_lock(pw.thread);
|
||||
pw.playback.stream = pw_stream_new_simple(
|
||||
pw.loop,
|
||||
"Looking Glass",
|
||||
pw_properties_new(
|
||||
PW_KEY_NODE_NAME , "Looking Glass",
|
||||
PW_KEY_MEDIA_TYPE , "Audio",
|
||||
PW_KEY_MEDIA_CATEGORY, "Playback",
|
||||
PW_KEY_MEDIA_ROLE , "Music",
|
||||
PW_KEY_NODE_LATENCY , requestedNodeLatency,
|
||||
NULL
|
||||
),
|
||||
&events,
|
||||
NULL
|
||||
);
|
||||
|
||||
// The user can override the default node latency with the PIPEWIRE_LATENCY
|
||||
// environment variable, so get the actual node latency value from the stream.
|
||||
// The actual quantum size may be lower than this value depending on what else
|
||||
// is using the audio device, but we can treat this value as a maximum
|
||||
const struct pw_properties * properties =
|
||||
pw_stream_get_properties(pw.playback.stream);
|
||||
const char *actualNodeLatency =
|
||||
pw_properties_get(properties, PW_KEY_NODE_LATENCY);
|
||||
DEBUG_ASSERT(actualNodeLatency != NULL);
|
||||
|
||||
unsigned num, denom;
|
||||
if (sscanf(actualNodeLatency, "%u/%u", &num, &denom) != 2 ||
|
||||
denom != sampleRate)
|
||||
{
|
||||
DEBUG_WARN(
|
||||
"PIPEWIRE_LATENCY value '%s' is invalid or does not match stream sample "
|
||||
"rate; using %d/%d", actualNodeLatency, requestedPeriodFrames,
|
||||
sampleRate);
|
||||
|
||||
struct spa_dict_item items[] = {
|
||||
{ PW_KEY_NODE_LATENCY, requestedNodeLatency }
|
||||
};
|
||||
pw_stream_update_properties(pw.playback.stream,
|
||||
&SPA_DICT_INIT_ARRAY(items));
|
||||
|
||||
pw.playback.maxPeriodFrames = requestedPeriodFrames;
|
||||
}
|
||||
else
|
||||
pw.playback.maxPeriodFrames = num;
|
||||
|
||||
// If the previous quantum size was very small, PipeWire can request two full
|
||||
// periods almost immediately at the start of playback
|
||||
pw.playback.startFrames = pw.playback.maxPeriodFrames * 2;
|
||||
|
||||
*maxPeriodFrames = pw.playback.maxPeriodFrames;
|
||||
*startFrames = pw.playback.startFrames;
|
||||
|
||||
if (!pw.playback.stream)
|
||||
{
|
||||
pw_thread_loop_unlock(pw.thread);
|
||||
DEBUG_ERROR("Failed to create the stream");
|
||||
return;
|
||||
}
|
||||
|
||||
params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat,
|
||||
&SPA_AUDIO_INFO_RAW_INIT(
|
||||
.format = SPA_AUDIO_FORMAT_F32,
|
||||
.channels = channels,
|
||||
.rate = sampleRate
|
||||
));
|
||||
|
||||
pw_stream_connect(
|
||||
pw.playback.stream,
|
||||
PW_DIRECTION_OUTPUT,
|
||||
PW_ID_ANY,
|
||||
PW_STREAM_FLAG_AUTOCONNECT |
|
||||
PW_STREAM_FLAG_MAP_BUFFERS |
|
||||
PW_STREAM_FLAG_RT_PROCESS |
|
||||
PW_STREAM_FLAG_INACTIVE,
|
||||
params, 1);
|
||||
|
||||
pw_thread_loop_unlock(pw.thread);
|
||||
}
|
||||
|
||||
static void pipewire_playbackStart(void)
|
||||
{
|
||||
if (!pw.playback.stream)
|
||||
return;
|
||||
|
||||
if (pw.playback.state != STREAM_STATE_ACTIVE)
|
||||
{
|
||||
pw_thread_loop_lock(pw.thread);
|
||||
|
||||
switch (pw.playback.state)
|
||||
{
|
||||
case STREAM_STATE_INACTIVE:
|
||||
pw_stream_set_active(pw.playback.stream, true);
|
||||
pw.playback.state = STREAM_STATE_ACTIVE;
|
||||
break;
|
||||
|
||||
case STREAM_STATE_DRAINING:
|
||||
// We are in the middle of draining the PipeWire buffers; we need to
|
||||
// wait for this to complete before allowing the new playback to start
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG_UNREACHABLE();
|
||||
}
|
||||
|
||||
pw_thread_loop_unlock(pw.thread);
|
||||
}
|
||||
}
|
||||
|
||||
static void pipewire_playbackStop(void)
|
||||
{
|
||||
if (pw.playback.state != STREAM_STATE_ACTIVE)
|
||||
return;
|
||||
|
||||
pw_thread_loop_lock(pw.thread);
|
||||
pw_stream_flush(pw.playback.stream, true);
|
||||
pw.playback.state = STREAM_STATE_DRAINING;
|
||||
pw_thread_loop_unlock(pw.thread);
|
||||
}
|
||||
|
||||
static void pipewire_playbackVolume(int channels, const uint16_t volume[])
|
||||
{
|
||||
if (channels != pw.playback.channels)
|
||||
return;
|
||||
|
||||
float param[channels];
|
||||
for(int i = 0; i < channels; ++i)
|
||||
param[i] = 9.3234e-7 * pow(1.000211902, volume[i]) - 0.000172787;
|
||||
|
||||
pw_thread_loop_lock(pw.thread);
|
||||
pw_stream_set_control(pw.playback.stream, SPA_PROP_channelVolumes,
|
||||
channels, param, 0);
|
||||
pw_thread_loop_unlock(pw.thread);
|
||||
}
|
||||
|
||||
static void pipewire_playbackMute(bool mute)
|
||||
{
|
||||
pw_thread_loop_lock(pw.thread);
|
||||
float val = mute ? 1.0f : 0.0f;
|
||||
pw_stream_set_control(pw.playback.stream, SPA_PROP_mute, 1, &val, 0);
|
||||
pw_thread_loop_unlock(pw.thread);
|
||||
}
|
||||
|
||||
static size_t pipewire_playbackLatency(void)
|
||||
{
|
||||
struct pw_time time = { 0 };
|
||||
|
||||
pw_thread_loop_lock(pw.thread);
|
||||
#if PW_CHECK_VERSION(0, 3, 50)
|
||||
if (pw_stream_get_time_n(pw.playback.stream, &time, sizeof(time)) < 0)
|
||||
#else
|
||||
if (pw_stream_get_time(pw.playback.stream, &time) < 0)
|
||||
#endif
|
||||
DEBUG_ERROR("pw_stream_get_time failed");
|
||||
pw_thread_loop_unlock(pw.thread);
|
||||
|
||||
return time.delay + time.queued / pw.playback.stride;
|
||||
}
|
||||
|
||||
static void pipewire_recordStopStream(void)
|
||||
{
|
||||
if (!pw.record.stream)
|
||||
return;
|
||||
|
||||
pw_thread_loop_lock(pw.thread);
|
||||
pw_stream_destroy(pw.record.stream);
|
||||
pw.record.stream = NULL;
|
||||
pw_thread_loop_unlock(pw.thread);
|
||||
}
|
||||
|
||||
static void pipewire_onRecordProcess(void * userdata)
|
||||
{
|
||||
struct pw_buffer * pbuf;
|
||||
|
||||
if (!(pbuf = pw_stream_dequeue_buffer(pw.record.stream)))
|
||||
{
|
||||
DEBUG_WARN("out of buffers");
|
||||
return;
|
||||
}
|
||||
|
||||
struct spa_buffer * sbuf = pbuf->buffer;
|
||||
uint8_t * dst;
|
||||
|
||||
if (!(dst = sbuf->datas[0].data))
|
||||
return;
|
||||
|
||||
dst += sbuf->datas[0].chunk->offset;
|
||||
pw.record.pushFn(dst,
|
||||
min(
|
||||
sbuf->datas[0].chunk->size,
|
||||
sbuf->datas[0].maxsize - sbuf->datas[0].chunk->offset) / pw.record.stride
|
||||
);
|
||||
|
||||
pw_stream_queue_buffer(pw.record.stream, pbuf);
|
||||
}
|
||||
|
||||
static void pipewire_recordStart(int channels, int sampleRate,
|
||||
LG_AudioPushFn pushFn)
|
||||
{
|
||||
const struct spa_pod * params[1];
|
||||
uint8_t buffer[1024];
|
||||
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
|
||||
static const struct pw_stream_events events =
|
||||
{
|
||||
.version = PW_VERSION_STREAM_EVENTS,
|
||||
.process = pipewire_onRecordProcess
|
||||
};
|
||||
|
||||
if (pw.record.stream &&
|
||||
pw.record.channels == channels &&
|
||||
pw.record.sampleRate == sampleRate)
|
||||
{
|
||||
if (!pw.record.active)
|
||||
{
|
||||
pw_thread_loop_lock(pw.thread);
|
||||
pw_stream_set_active(pw.record.stream, true);
|
||||
pw.record.active = true;
|
||||
pw_thread_loop_unlock(pw.thread);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
pipewire_recordStopStream();
|
||||
|
||||
pw.record.channels = channels;
|
||||
pw.record.sampleRate = sampleRate;
|
||||
pw.record.stride = sizeof(uint16_t) * channels;
|
||||
pw.record.pushFn = pushFn;
|
||||
|
||||
pw_thread_loop_lock(pw.thread);
|
||||
pw.record.stream = pw_stream_new_simple(
|
||||
pw.loop,
|
||||
"Looking Glass",
|
||||
pw_properties_new(
|
||||
PW_KEY_NODE_NAME , "Looking Glass",
|
||||
PW_KEY_MEDIA_TYPE , "Audio",
|
||||
PW_KEY_MEDIA_CATEGORY, "Capture",
|
||||
PW_KEY_MEDIA_ROLE , "Music",
|
||||
NULL
|
||||
),
|
||||
&events,
|
||||
NULL
|
||||
);
|
||||
|
||||
if (!pw.record.stream)
|
||||
{
|
||||
pw_thread_loop_unlock(pw.thread);
|
||||
DEBUG_ERROR("Failed to create the stream");
|
||||
return;
|
||||
}
|
||||
|
||||
params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat,
|
||||
&SPA_AUDIO_INFO_RAW_INIT(
|
||||
.format = SPA_AUDIO_FORMAT_S16,
|
||||
.channels = channels,
|
||||
.rate = sampleRate
|
||||
));
|
||||
|
||||
pw_stream_connect(
|
||||
pw.record.stream,
|
||||
PW_DIRECTION_INPUT,
|
||||
PW_ID_ANY,
|
||||
PW_STREAM_FLAG_AUTOCONNECT |
|
||||
PW_STREAM_FLAG_MAP_BUFFERS |
|
||||
PW_STREAM_FLAG_RT_PROCESS,
|
||||
params, 1);
|
||||
|
||||
pw_thread_loop_unlock(pw.thread);
|
||||
pw.record.active = true;
|
||||
}
|
||||
|
||||
static void pipewire_recordStop(void)
|
||||
{
|
||||
if (!pw.record.active)
|
||||
return;
|
||||
|
||||
pw_thread_loop_lock(pw.thread);
|
||||
pw_stream_set_active(pw.record.stream, false);
|
||||
pw.record.active = false;
|
||||
pw_thread_loop_unlock(pw.thread);
|
||||
}
|
||||
|
||||
static void pipewire_recordVolume(int channels, const uint16_t volume[])
|
||||
{
|
||||
if (channels != pw.record.channels)
|
||||
return;
|
||||
|
||||
float param[channels];
|
||||
for(int i = 0; i < channels; ++i)
|
||||
param[i] = 9.3234e-7 * pow(1.000211902, volume[i]) - 0.000172787;
|
||||
|
||||
pw_thread_loop_lock(pw.thread);
|
||||
pw_stream_set_control(pw.record.stream, SPA_PROP_channelVolumes,
|
||||
channels, param, 0);
|
||||
pw_thread_loop_unlock(pw.thread);
|
||||
}
|
||||
|
||||
static void pipewire_recordMute(bool mute)
|
||||
{
|
||||
pw_thread_loop_lock(pw.thread);
|
||||
float val = mute ? 1.0f : 0.0f;
|
||||
pw_stream_set_control(pw.record.stream, SPA_PROP_mute, 1, &val, 0);
|
||||
pw_thread_loop_unlock(pw.thread);
|
||||
}
|
||||
|
||||
static void pipewire_free(void)
|
||||
{
|
||||
pipewire_playbackStopStream();
|
||||
pipewire_recordStopStream();
|
||||
pw_thread_loop_stop(pw.thread);
|
||||
pw_thread_loop_destroy(pw.thread);
|
||||
pw_context_destroy(pw.context);
|
||||
pw_loop_destroy(pw.loop);
|
||||
|
||||
pw.loop = NULL;
|
||||
pw.context = NULL;
|
||||
pw.thread = NULL;
|
||||
|
||||
pw_deinit();
|
||||
}
|
||||
|
||||
struct LG_AudioDevOps LGAD_PipeWire =
|
||||
{
|
||||
.name = "PipeWire",
|
||||
.init = pipewire_init,
|
||||
.free = pipewire_free,
|
||||
.playback =
|
||||
{
|
||||
.setup = pipewire_playbackSetup,
|
||||
.start = pipewire_playbackStart,
|
||||
.stop = pipewire_playbackStop,
|
||||
.volume = pipewire_playbackVolume,
|
||||
.mute = pipewire_playbackMute,
|
||||
.latency = pipewire_playbackLatency
|
||||
},
|
||||
.record =
|
||||
{
|
||||
.start = pipewire_recordStart,
|
||||
.stop = pipewire_recordStop,
|
||||
.volume = pipewire_recordVolume,
|
||||
.mute = pipewire_recordMute
|
||||
}
|
||||
};
|
||||
21
client/audiodevs/PulseAudio/CMakeLists.txt
Normal file
21
client/audiodevs/PulseAudio/CMakeLists.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(audiodev_PulseAudio LANGUAGES C)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(AUDIODEV_PulseAudio REQUIRED IMPORTED_TARGET
|
||||
libpulse
|
||||
)
|
||||
|
||||
add_library(audiodev_PulseAudio STATIC
|
||||
pulseaudio.c
|
||||
)
|
||||
|
||||
target_link_libraries(audiodev_PulseAudio
|
||||
PkgConfig::AUDIODEV_PulseAudio
|
||||
lg_common
|
||||
)
|
||||
|
||||
target_include_directories(audiodev_PulseAudio
|
||||
PRIVATE
|
||||
src
|
||||
)
|
||||
393
client/audiodevs/PulseAudio/pulseaudio.c
Normal file
393
client/audiodevs/PulseAudio/pulseaudio.c
Normal file
@@ -0,0 +1,393 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2022 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/audiodev.h"
|
||||
|
||||
#include <pulse/pulseaudio.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
struct PulseAudio
|
||||
{
|
||||
pa_threaded_mainloop * loop;
|
||||
pa_mainloop_api * api;
|
||||
pa_context * context;
|
||||
pa_operation * contextSub;
|
||||
|
||||
pa_stream * sink;
|
||||
int sinkIndex;
|
||||
bool sinkCorked;
|
||||
bool sinkMuted;
|
||||
bool sinkStarting;
|
||||
int sinkMaxPeriodFrames;
|
||||
int sinkStartFrames;
|
||||
int sinkSampleRate;
|
||||
int sinkChannels;
|
||||
int sinkStride;
|
||||
LG_AudioPullFn sinkPullFn;
|
||||
};
|
||||
|
||||
static struct PulseAudio pa = {0};
|
||||
|
||||
static void pulseaudio_sink_input_cb(pa_context *c, const pa_sink_input_info *i,
|
||||
int eol, void *userdata)
|
||||
{
|
||||
if (eol < 0 || eol == 1)
|
||||
return;
|
||||
|
||||
pa.sinkIndex = i->index;
|
||||
}
|
||||
|
||||
static void pulseaudio_subscribe_cb(pa_context *c,
|
||||
pa_subscription_event_type_t t, uint32_t index, void *userdata)
|
||||
{
|
||||
switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK)
|
||||
{
|
||||
case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
|
||||
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE)
|
||||
pa.sinkIndex = 0;
|
||||
else
|
||||
{
|
||||
pa_operation *o = pa_context_get_sink_input_info(c, index,
|
||||
pulseaudio_sink_input_cb, NULL);
|
||||
pa_operation_unref(o);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void pulseaudio_ctx_state_change_cb(pa_context * c, void * userdata)
|
||||
{
|
||||
switch (pa_context_get_state(c))
|
||||
{
|
||||
case PA_CONTEXT_CONNECTING:
|
||||
case PA_CONTEXT_AUTHORIZING:
|
||||
case PA_CONTEXT_SETTING_NAME:
|
||||
break;
|
||||
|
||||
case PA_CONTEXT_READY:
|
||||
DEBUG_INFO("Connected to PulseAudio server");
|
||||
pa_context_set_subscribe_callback(c, pulseaudio_subscribe_cb, NULL);
|
||||
pa_context_subscribe(c, PA_SUBSCRIPTION_MASK_SINK_INPUT, NULL, NULL);
|
||||
pa_threaded_mainloop_signal(pa.loop, 0);
|
||||
break;
|
||||
|
||||
case PA_CONTEXT_TERMINATED:
|
||||
if (pa.contextSub)
|
||||
{
|
||||
pa_operation_unref(pa.contextSub);
|
||||
pa.contextSub = NULL;
|
||||
}
|
||||
break;
|
||||
|
||||
case PA_CONTEXT_FAILED:
|
||||
default:
|
||||
DEBUG_ERROR("context error: %s", pa_strerror(pa_context_errno(c)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool pulseaudio_init(void)
|
||||
{
|
||||
pa.loop = pa_threaded_mainloop_new();
|
||||
if (!pa.loop)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the main loop");
|
||||
goto err;
|
||||
}
|
||||
|
||||
pa.api = pa_threaded_mainloop_get_api(pa.loop);
|
||||
if (pa_signal_init(pa.api) != 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to init signals");
|
||||
goto err_loop;
|
||||
}
|
||||
|
||||
if (pa_threaded_mainloop_start(pa.loop) < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to start the main loop");
|
||||
goto err_loop;
|
||||
}
|
||||
|
||||
pa_proplist * propList = pa_proplist_new();
|
||||
if (!propList)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the proplist");
|
||||
goto err_thread;
|
||||
}
|
||||
pa_proplist_sets(propList, PA_PROP_MEDIA_ROLE, "video");
|
||||
|
||||
pa_threaded_mainloop_lock(pa.loop);
|
||||
pa.context = pa_context_new_with_proplist(
|
||||
pa.api,
|
||||
"Looking Glass",
|
||||
propList);
|
||||
if (!pa.context)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the context");
|
||||
goto err_context;
|
||||
}
|
||||
|
||||
pa_context_set_state_callback(pa.context,
|
||||
pulseaudio_ctx_state_change_cb, NULL);
|
||||
|
||||
if (pa_context_connect(pa.context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL) < 0)
|
||||
{
|
||||
DEBUG_ERROR("Failed to connect to the context server");
|
||||
goto err_context;
|
||||
}
|
||||
|
||||
for(;;)
|
||||
{
|
||||
pa_context_state_t state = pa_context_get_state(pa.context);
|
||||
if(!PA_CONTEXT_IS_GOOD(state))
|
||||
{
|
||||
DEBUG_ERROR("Context is bad");
|
||||
goto err_context;
|
||||
}
|
||||
|
||||
if (state == PA_CONTEXT_READY)
|
||||
break;
|
||||
|
||||
pa_threaded_mainloop_wait(pa.loop);
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_unlock(pa.loop);
|
||||
pa_proplist_free(propList);
|
||||
return true;
|
||||
|
||||
err_context:
|
||||
pa_threaded_mainloop_unlock(pa.loop);
|
||||
pa_proplist_free(propList);
|
||||
|
||||
err_thread:
|
||||
pa_threaded_mainloop_stop(pa.loop);
|
||||
|
||||
err_loop:
|
||||
pa_threaded_mainloop_free(pa.loop);
|
||||
|
||||
err:
|
||||
return false;
|
||||
}
|
||||
|
||||
static void pulseaudio_sink_close_nl(void)
|
||||
{
|
||||
if (!pa.sink)
|
||||
return;
|
||||
|
||||
pa_stream_set_write_callback(pa.sink, NULL, NULL);
|
||||
pa_stream_flush(pa.sink, NULL, NULL);
|
||||
pa_stream_unref(pa.sink);
|
||||
pa.sink = NULL;
|
||||
}
|
||||
|
||||
static void pulseaudio_free(void)
|
||||
{
|
||||
pa_threaded_mainloop_lock(pa.loop);
|
||||
|
||||
pulseaudio_sink_close_nl();
|
||||
|
||||
pa_context_set_state_callback(pa.context, NULL, NULL);
|
||||
pa_context_set_subscribe_callback(pa.context, NULL, NULL);
|
||||
pa_context_disconnect(pa.context);
|
||||
pa_context_unref(pa.context);
|
||||
|
||||
if (pa.contextSub)
|
||||
{
|
||||
pa_operation_unref(pa.contextSub);
|
||||
pa.contextSub = NULL;
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_unlock(pa.loop);
|
||||
}
|
||||
|
||||
static void pulseaudio_state_cb(pa_stream * p, void * userdata)
|
||||
{
|
||||
if (pa.sinkStarting && pa_stream_get_state(pa.sink) == PA_STREAM_READY)
|
||||
{
|
||||
pa_stream_cork(pa.sink, 0, NULL, NULL);
|
||||
pa.sinkCorked = false;
|
||||
pa.sinkStarting = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void pulseaudio_write_cb(pa_stream * p, size_t nbytes, void * userdata)
|
||||
{
|
||||
// PulseAudio tries to pull data from the stream as soon as it is created for
|
||||
// some reason, even though it is corked
|
||||
if (pa.sinkCorked)
|
||||
return;
|
||||
|
||||
uint8_t * dst;
|
||||
|
||||
pa_stream_begin_write(p, (void **)&dst, &nbytes);
|
||||
|
||||
int frames = nbytes / pa.sinkStride;
|
||||
frames = pa.sinkPullFn(dst, frames);
|
||||
|
||||
pa_stream_write(p, dst, frames * pa.sinkStride, NULL, 0, PA_SEEK_RELATIVE);
|
||||
}
|
||||
|
||||
static void pulseaudio_underflow_cb(pa_stream * p, void * userdata)
|
||||
{
|
||||
DEBUG_WARN("Underflow");
|
||||
}
|
||||
|
||||
static void pulseaudio_overflow_cb(pa_stream * p, void * userdata)
|
||||
{
|
||||
DEBUG_WARN("Overflow");
|
||||
}
|
||||
|
||||
static void pulseaudio_setup(int channels, int sampleRate,
|
||||
int requestedPeriodFrames, int * maxPeriodFrames, int * startFrames,
|
||||
LG_AudioPullFn pullFn)
|
||||
{
|
||||
if (pa.sink && pa.sinkChannels == channels && pa.sinkSampleRate == sampleRate)
|
||||
{
|
||||
*maxPeriodFrames = pa.sinkMaxPeriodFrames;
|
||||
*startFrames = pa.sinkStartFrames;
|
||||
return;
|
||||
}
|
||||
|
||||
pa_sample_spec spec = {
|
||||
.format = PA_SAMPLE_FLOAT32,
|
||||
.rate = sampleRate,
|
||||
.channels = channels
|
||||
};
|
||||
|
||||
int stride = channels * sizeof(float);
|
||||
int bufferSize = requestedPeriodFrames * 2 * stride;
|
||||
pa_buffer_attr attribs =
|
||||
{
|
||||
.maxlength = -1,
|
||||
.tlength = bufferSize,
|
||||
.prebuf = 0,
|
||||
.minreq = (uint32_t)-1
|
||||
};
|
||||
|
||||
pa_threaded_mainloop_lock(pa.loop);
|
||||
pulseaudio_sink_close_nl();
|
||||
|
||||
pa.sinkChannels = channels;
|
||||
pa.sinkSampleRate = sampleRate;
|
||||
|
||||
pa.sink = pa_stream_new(pa.context, "Looking Glass", &spec, NULL);
|
||||
pa_stream_set_state_callback (pa.sink, pulseaudio_state_cb , NULL);
|
||||
pa_stream_set_write_callback (pa.sink, pulseaudio_write_cb , NULL);
|
||||
pa_stream_set_underflow_callback(pa.sink, pulseaudio_underflow_cb, NULL);
|
||||
pa_stream_set_overflow_callback (pa.sink, pulseaudio_overflow_cb , NULL);
|
||||
|
||||
pa_stream_connect_playback(pa.sink, NULL, &attribs, PA_STREAM_START_CORKED,
|
||||
NULL, NULL);
|
||||
|
||||
pa.sinkStride = stride;
|
||||
pa.sinkPullFn = pullFn;
|
||||
pa.sinkMaxPeriodFrames = requestedPeriodFrames;
|
||||
pa.sinkCorked = true;
|
||||
pa.sinkStarting = false;
|
||||
|
||||
// If something else is, or was recently using a small latency value,
|
||||
// PulseAudio can request way more data at startup than is reasonable
|
||||
pa.sinkStartFrames = requestedPeriodFrames * 4;
|
||||
|
||||
*maxPeriodFrames = requestedPeriodFrames;
|
||||
*startFrames = pa.sinkStartFrames;
|
||||
|
||||
pa_threaded_mainloop_unlock(pa.loop);
|
||||
}
|
||||
|
||||
static void pulseaudio_start(void)
|
||||
{
|
||||
if (!pa.sink)
|
||||
return;
|
||||
|
||||
pa_threaded_mainloop_lock(pa.loop);
|
||||
|
||||
pa_stream_state_t state = pa_stream_get_state(pa.sink);
|
||||
if (state == PA_STREAM_CREATING)
|
||||
pa.sinkStarting = true;
|
||||
else
|
||||
{
|
||||
pa_stream_cork(pa.sink, 0, NULL, NULL);
|
||||
pa.sinkCorked = false;
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_unlock(pa.loop);
|
||||
}
|
||||
|
||||
static void pulseaudio_stop(void)
|
||||
{
|
||||
if (!pa.sink)
|
||||
return;
|
||||
|
||||
bool needLock = !pa_threaded_mainloop_in_thread(pa.loop);
|
||||
if (needLock)
|
||||
pa_threaded_mainloop_lock(pa.loop);
|
||||
|
||||
pa_stream_cork(pa.sink, 1, NULL, NULL);
|
||||
pa.sinkCorked = true;
|
||||
pa.sinkStarting = false;
|
||||
|
||||
if (needLock)
|
||||
pa_threaded_mainloop_unlock(pa.loop);
|
||||
}
|
||||
|
||||
static void pulseaudio_volume(int channels, const uint16_t volume[])
|
||||
{
|
||||
if (!pa.sink || !pa.sinkIndex)
|
||||
return;
|
||||
|
||||
struct pa_cvolume v = { .channels = channels };
|
||||
for(int i = 0; i < channels; ++i)
|
||||
v.values[i] = pa_sw_volume_from_linear(
|
||||
9.3234e-7 * pow(1.000211902, volume[i]) - 0.000172787);
|
||||
|
||||
pa_threaded_mainloop_lock(pa.loop);
|
||||
pa_context_set_sink_input_volume(pa.context, pa.sinkIndex, &v, NULL, NULL);
|
||||
pa_threaded_mainloop_unlock(pa.loop);
|
||||
}
|
||||
|
||||
static void pulseaudio_mute(bool mute)
|
||||
{
|
||||
if (!pa.sink || !pa.sinkIndex || pa.sinkMuted == mute)
|
||||
return;
|
||||
|
||||
pa.sinkMuted = mute;
|
||||
pa_threaded_mainloop_lock(pa.loop);
|
||||
pa_context_set_sink_input_mute(pa.context, pa.sinkIndex, mute, NULL, NULL);
|
||||
pa_threaded_mainloop_unlock(pa.loop);
|
||||
}
|
||||
|
||||
struct LG_AudioDevOps LGAD_PulseAudio =
|
||||
{
|
||||
.name = "PulseAudio",
|
||||
.init = pulseaudio_init,
|
||||
.free = pulseaudio_free,
|
||||
.playback =
|
||||
{
|
||||
.setup = pulseaudio_setup,
|
||||
.start = pulseaudio_start,
|
||||
.stop = pulseaudio_stop,
|
||||
.volume = pulseaudio_volume,
|
||||
.mute = pulseaudio_mute
|
||||
}
|
||||
};
|
||||
@@ -23,6 +23,7 @@ else()
|
||||
endif()
|
||||
|
||||
add_library(displayserver_Wayland STATIC
|
||||
activation.c
|
||||
clipboard.c
|
||||
cursor.c
|
||||
gl.c
|
||||
@@ -50,8 +51,6 @@ target_include_directories(displayserver_Wayland
|
||||
)
|
||||
|
||||
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"
|
||||
@@ -67,6 +66,7 @@ macro(wayland_generate protocol_file output_file)
|
||||
target_sources(displayserver_Wayland PRIVATE "${output_file}.h" "${output_file}.c")
|
||||
endmacro()
|
||||
|
||||
set(WAYLAND_PROTOCOLS_BASE "${PROJECT_TOP}/repos/wayland-protocols")
|
||||
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/wayland")
|
||||
include_directories("${CMAKE_BINARY_DIR}/wayland")
|
||||
wayland_generate(
|
||||
@@ -96,3 +96,6 @@ wayland_generate(
|
||||
wayland_generate(
|
||||
"${WAYLAND_PROTOCOLS_BASE}/unstable/xdg-output/xdg-output-unstable-v1.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-xdg-output-unstable-v1-client-protocol")
|
||||
wayland_generate(
|
||||
"${WAYLAND_PROTOCOLS_BASE}/staging/xdg-activation/xdg-activation-v1.xml"
|
||||
"${CMAKE_BINARY_DIR}/wayland/wayland-xdg-activation-v1-client-protocol")
|
||||
|
||||
71
client/displayservers/Wayland/activation.c
Normal file
71
client/displayservers/Wayland/activation.c
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2022 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 waylandActivationInit(void)
|
||||
{
|
||||
if (!wlWm.xdgActivation)
|
||||
DEBUG_WARN("xdg_activation_v1 not exported by compositor, will not be able "
|
||||
"to request host focus on behalf of guest applications");
|
||||
return true;
|
||||
}
|
||||
|
||||
void waylandActivationFree(void)
|
||||
{
|
||||
if (wlWm.xdgActivation)
|
||||
{
|
||||
xdg_activation_v1_destroy(wlWm.xdgActivation);
|
||||
}
|
||||
}
|
||||
|
||||
static void activationTokenDone(void * data,
|
||||
struct xdg_activation_token_v1 * xdgToken, const char * token)
|
||||
{
|
||||
xdg_activation_v1_activate(wlWm.xdgActivation, token, wlWm.surface);
|
||||
xdg_activation_token_v1_destroy(xdgToken);
|
||||
}
|
||||
|
||||
static const struct xdg_activation_token_v1_listener activationTokenListener = {
|
||||
.done = &activationTokenDone,
|
||||
};
|
||||
|
||||
void waylandActivationRequestActivation(void)
|
||||
{
|
||||
if (!wlWm.xdgActivation) return;
|
||||
|
||||
struct xdg_activation_token_v1 * token =
|
||||
xdg_activation_v1_get_activation_token(wlWm.xdgActivation);
|
||||
|
||||
if (!token)
|
||||
{
|
||||
DEBUG_ERROR("failed to retrieve XDG activation token");
|
||||
return;
|
||||
}
|
||||
|
||||
xdg_activation_token_v1_add_listener(token, &activationTokenListener, NULL);
|
||||
xdg_activation_token_v1_set_surface(token, wlWm.surface);
|
||||
xdg_activation_token_v1_commit(token);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -616,7 +616,10 @@ void waylandRealignPointer(void)
|
||||
|
||||
void waylandGuestPointerUpdated(double x, double y, double localX, double localY)
|
||||
{
|
||||
if (!wlWm.warpSupport || !wlWm.pointerInSurface || wlWm.lockedPointer)
|
||||
if ( !wlWm.pointer ||
|
||||
!wlWm.warpSupport ||
|
||||
!wlWm.pointerInSurface ||
|
||||
wlWm.lockedPointer )
|
||||
return;
|
||||
|
||||
waylandWarpPointer((int) localX, (int) localY, false);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -86,7 +86,8 @@ bool waylandPresentationInit(void)
|
||||
if (wlWm.presentation)
|
||||
{
|
||||
wlWm.photonTimings = ringbuffer_new(256, sizeof(float));
|
||||
wlWm.photonGraph = app_registerGraph("PHOTON", wlWm.photonTimings, 0.0f, 30.0f);
|
||||
wlWm.photonGraph = app_registerGraph("PHOTON", wlWm.photonTimings,
|
||||
0.0f, 30.0f, NULL);
|
||||
wp_presentation_add_listener(wlWm.presentation, &presentationListener, NULL);
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -72,6 +72,9 @@ static void registryGlobalHandler(void * data, struct wl_registry * registry,
|
||||
wlWm.xdgOutputManager = wl_registry_bind(wlWm.registry, name,
|
||||
// we only need v2 to run, but v3 saves a callback
|
||||
&zxdg_output_manager_v1_interface, version > 3 ? 3 : version);
|
||||
else if (!strcmp(interface, xdg_activation_v1_interface.name))
|
||||
wlWm.xdgActivation = wl_registry_bind(wlWm.registry, name,
|
||||
&xdg_activation_v1_interface, 1);
|
||||
}
|
||||
|
||||
static void registryGlobalRemoveHandler(void * data,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -89,6 +89,9 @@ static bool waylandInit(const LG_DSInitParams params)
|
||||
if (!waylandRegistryInit())
|
||||
return false;
|
||||
|
||||
if (!waylandActivationInit())
|
||||
return false;
|
||||
|
||||
if (!waylandIdleInit())
|
||||
return false;
|
||||
|
||||
@@ -187,6 +190,7 @@ struct LG_DisplayServerOps LGDS_Wayland =
|
||||
.warpPointer = waylandWarpPointer,
|
||||
.realignPointer = waylandRealignPointer,
|
||||
.isValidPointerPos = waylandIsValidPointerPos,
|
||||
.requestActivation = waylandActivationRequestActivation,
|
||||
.inhibitIdle = waylandInhibitIdle,
|
||||
.uninhibitIdle = waylandUninhibitIdle,
|
||||
.wait = waylandWait,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -47,6 +47,7 @@
|
||||
#include "wayland-relative-pointer-unstable-v1-client-protocol.h"
|
||||
#include "wayland-idle-inhibit-unstable-v1-client-protocol.h"
|
||||
#include "wayland-xdg-output-unstable-v1-client-protocol.h"
|
||||
#include "wayland-xdg-activation-v1-client-protocol.h"
|
||||
|
||||
typedef void (*WaylandPollCallback)(uint32_t events, void * opaque);
|
||||
|
||||
@@ -180,6 +181,8 @@ struct WaylandDSState
|
||||
struct zwp_idle_inhibit_manager_v1 * idleInhibitManager;
|
||||
struct zwp_idle_inhibitor_v1 * idleInhibitor;
|
||||
|
||||
struct xdg_activation_v1 * xdgActivation;
|
||||
|
||||
struct wp_viewporter * viewporter;
|
||||
struct wp_viewport * viewport;
|
||||
struct zxdg_output_manager_v1 * xdgOutputManager;
|
||||
@@ -231,6 +234,11 @@ struct WCBState
|
||||
extern struct WaylandDSState wlWm;
|
||||
extern struct WCBState wlCb;
|
||||
|
||||
// activation module
|
||||
bool waylandActivationInit(void);
|
||||
void waylandActivationFree(void);
|
||||
void waylandActivationRequestActivation(void);
|
||||
|
||||
// clipboard module
|
||||
bool waylandCBInit(void);
|
||||
void waylandCBRequest(LG_ClipboardData type);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -57,6 +57,12 @@ void waylandWindowUpdateScale(void)
|
||||
static void wlSurfaceEnterHandler(void * data, struct wl_surface * surface, struct wl_output * output)
|
||||
{
|
||||
struct SurfaceOutput * node = malloc(sizeof(*node));
|
||||
if (!node)
|
||||
{
|
||||
DEBUG_ERROR("out of memory");
|
||||
return;
|
||||
}
|
||||
|
||||
node->output = output;
|
||||
wl_list_insert(&wlWm.surfaceOutputs, &node->link);
|
||||
waylandWindowUpdateScale();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -28,11 +28,13 @@
|
||||
DEF_ATOM(_NET_REQUEST_FRAME_EXTENTS, True) \
|
||||
DEF_ATOM(_NET_FRAME_EXTENTS, True) \
|
||||
DEF_ATOM(_NET_WM_BYPASS_COMPOSITOR, False) \
|
||||
DEF_ATOM(_NET_WM_ICON, True) \
|
||||
DEF_ATOM(_NET_WM_STATE, True) \
|
||||
DEF_ATOM(_NET_WM_STATE_FULLSCREEN, True) \
|
||||
DEF_ATOM(_NET_WM_STATE_FOCUSED, True) \
|
||||
DEF_ATOM(_NET_WM_STATE_MAXIMIZED_HORZ, True) \
|
||||
DEF_ATOM(_NET_WM_STATE_MAXIMIZED_VERT, True) \
|
||||
DEF_ATOM(_NET_WM_STATE_DEMANDS_ATTENTION, True) \
|
||||
DEF_ATOM(_NET_WM_WINDOW_TYPE, True) \
|
||||
DEF_ATOM(_NET_WM_WINDOW_TYPE_NORMAL, True) \
|
||||
DEF_ATOM(_NET_WM_WINDOW_TYPE_UTILITY, True) \
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -123,8 +123,6 @@ bool x11CBInit()
|
||||
return false;
|
||||
}
|
||||
|
||||
XFixesSelectSelectionInput(x11.display, x11.window,
|
||||
XA_PRIMARY, XFixesSetSelectionOwnerNotifyMask);
|
||||
XFixesSelectSelectionInput(x11.display, x11.window,
|
||||
x11atoms.CLIPBOARD, XFixesSetSelectionOwnerNotifyMask);
|
||||
|
||||
@@ -154,6 +152,12 @@ static void x11CBReplyFn(void * opaque, LG_ClipboardData type,
|
||||
static void x11CBSelectionRequest(const XSelectionRequestEvent e)
|
||||
{
|
||||
XEvent * s = malloc(sizeof(*s));
|
||||
if (!s)
|
||||
{
|
||||
DEBUG_ERROR("out of memory");
|
||||
return;
|
||||
}
|
||||
|
||||
s->xselection.type = SelectionNotify;
|
||||
s->xselection.requestor = e.requestor;
|
||||
s->xselection.selection = e.selection;
|
||||
@@ -205,7 +209,7 @@ send:
|
||||
|
||||
static void x11CBSelectionClear(const XSelectionClearEvent e)
|
||||
{
|
||||
if (e.selection != XA_PRIMARY && e.selection != x11atoms.CLIPBOARD)
|
||||
if (e.selection != x11atoms.CLIPBOARD)
|
||||
return;
|
||||
|
||||
x11cb.aCurSelection = BadValue;
|
||||
@@ -291,7 +295,7 @@ out:
|
||||
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) ||
|
||||
if (e.selection != x11atoms.CLIPBOARD ||
|
||||
e.owner == x11.window || e.owner == 0)
|
||||
{
|
||||
return;
|
||||
@@ -396,7 +400,6 @@ 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);
|
||||
}
|
||||
@@ -404,7 +407,6 @@ void x11CBNotice(LG_ClipboardData type)
|
||||
void x11CBRelease(void)
|
||||
{
|
||||
x11cb.haveRequest = false;
|
||||
XSetSelectionOwner(x11.display, XA_PRIMARY , None, CurrentTime);
|
||||
XSetSelectionOwner(x11.display, x11atoms.CLIPBOARD, None, CurrentTime);
|
||||
XFlush(x11.display);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
bool x11CBEventThread(const XEvent xe);
|
||||
|
||||
bool x11CBInit();
|
||||
bool x11CBInit(void);
|
||||
void x11CBNotice(LG_ClipboardData type);
|
||||
void x11CBRelease(void);
|
||||
void x11CBRequest(LG_ClipboardData type);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "x11.h"
|
||||
#include "atoms.h"
|
||||
#include "clipboard.h"
|
||||
#include "resources/icondata.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
@@ -532,11 +533,8 @@ static bool x11Init(const LG_DSInitParams params)
|
||||
eventmask.mask_len = sizeof(mask);
|
||||
eventmask.mask = mask;
|
||||
|
||||
if (!x11.ewmhHasFocusEvent)
|
||||
{
|
||||
XISetMask(mask, XI_FocusIn );
|
||||
XISetMask(mask, XI_FocusOut);
|
||||
}
|
||||
XISetMask(mask, XI_FocusIn );
|
||||
XISetMask(mask, XI_FocusOut);
|
||||
|
||||
XISetMask(mask, XI_Enter );
|
||||
XISetMask(mask, XI_Leave );
|
||||
@@ -565,6 +563,17 @@ static bool x11Init(const LG_DSInitParams params)
|
||||
1
|
||||
);
|
||||
|
||||
XChangeProperty(
|
||||
x11.display,
|
||||
x11.window,
|
||||
x11atoms._NET_WM_ICON,
|
||||
XA_CARDINAL,
|
||||
32,
|
||||
PropModeReplace,
|
||||
(unsigned char *)icondata,
|
||||
sizeof(icondata) / sizeof(icondata[0])
|
||||
);
|
||||
|
||||
/* create the blank cursor */
|
||||
{
|
||||
static char data[] = { 0x00 };
|
||||
@@ -1022,6 +1031,16 @@ static void updateModifiers(void)
|
||||
);
|
||||
}
|
||||
|
||||
static void setFocus(bool focused, double x, double y)
|
||||
{
|
||||
if (x11.focused == focused)
|
||||
return;
|
||||
|
||||
x11.focused = focused;
|
||||
app_updateCursorPos(x, y);
|
||||
app_handleFocusEvent(focused);
|
||||
}
|
||||
|
||||
static void x11XInputEvent(XGenericEventCookie *cookie)
|
||||
{
|
||||
static int button_state = 0;
|
||||
@@ -1030,43 +1049,50 @@ static void x11XInputEvent(XGenericEventCookie *cookie)
|
||||
{
|
||||
case XI_FocusIn:
|
||||
{
|
||||
XIFocusOutEvent *xie = cookie->data;
|
||||
if (x11.ewmhHasFocusEvent)
|
||||
{
|
||||
// if meta ungrab for move/resize
|
||||
if (xie->mode == XINotifyUngrab)
|
||||
setFocus(true, xie->event_x, xie->event_y);
|
||||
return;
|
||||
}
|
||||
|
||||
atomic_store(&x11.lastWMEvent, microtime());
|
||||
if (x11.focused)
|
||||
return;
|
||||
|
||||
XIFocusOutEvent *xie = cookie->data;
|
||||
if (xie->mode != XINotifyNormal &&
|
||||
xie->mode != XINotifyWhileGrabbed &&
|
||||
xie->mode != XINotifyUngrab)
|
||||
return;
|
||||
|
||||
x11.focused = true;
|
||||
app_updateCursorPos(xie->event_x, xie->event_y);
|
||||
app_handleFocusEvent(true);
|
||||
|
||||
setFocus(true, xie->event_x, xie->event_y);
|
||||
return;
|
||||
}
|
||||
|
||||
case XI_FocusOut:
|
||||
{
|
||||
XIFocusOutEvent *xie = cookie->data;
|
||||
if (x11.ewmhHasFocusEvent)
|
||||
{
|
||||
// if meta grab for move/resize
|
||||
if (xie->mode == XINotifyGrab)
|
||||
setFocus(false, xie->event_x, xie->event_y);
|
||||
return;
|
||||
}
|
||||
|
||||
atomic_store(&x11.lastWMEvent, microtime());
|
||||
if (!x11.focused)
|
||||
return;
|
||||
|
||||
XIFocusOutEvent *xie = cookie->data;
|
||||
if (xie->mode != XINotifyNormal &&
|
||||
xie->mode != XINotifyWhileGrabbed &&
|
||||
xie->mode != XINotifyGrab)
|
||||
return;
|
||||
|
||||
app_updateCursorPos(xie->event_x, xie->event_y);
|
||||
app_handleFocusEvent(false);
|
||||
x11.focused = false;
|
||||
setFocus(false, xie->event_x, xie->event_y);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1799,6 +1825,28 @@ static bool x11IsValidPointerPos(int x, int y)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void x11RequestActivation(void)
|
||||
{
|
||||
XEvent e =
|
||||
{
|
||||
.xclient = {
|
||||
.type = ClientMessage,
|
||||
.send_event = true,
|
||||
.message_type = x11atoms._NET_WM_STATE,
|
||||
.format = 32,
|
||||
.window = x11.window,
|
||||
.data.l = {
|
||||
_NET_WM_STATE_ADD,
|
||||
x11atoms._NET_WM_STATE_DEMANDS_ATTENTION,
|
||||
0
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
XSendEvent(x11.display, DefaultRootWindow(x11.display), False,
|
||||
SubstructureNotifyMask | SubstructureRedirectMask, &e);
|
||||
}
|
||||
|
||||
static void x11InhibitIdle(void)
|
||||
{
|
||||
XScreenSaverSuspend(x11.display, true);
|
||||
@@ -1889,6 +1937,7 @@ struct LG_DisplayServerOps LGDS_X11 =
|
||||
.warpPointer = x11WarpPointer,
|
||||
.realignPointer = x11RealignPointer,
|
||||
.isValidPointerPos = x11IsValidPointerPos,
|
||||
.requestActivation = x11RequestActivation,
|
||||
.inhibitIdle = x11InhibitIdle,
|
||||
.uninhibitIdle = x11UninhibitIdle,
|
||||
.wait = x11Wait,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -102,11 +102,21 @@ int app_renderOverlay(struct Rect * rects, int maxRects);
|
||||
|
||||
void app_freeOverlays(void);
|
||||
|
||||
/**
|
||||
* invalidate the window to update the overlay, if renderTwice is set the imgui
|
||||
* render code will run twice so that auto sized windows are calculated correctly
|
||||
*/
|
||||
void app_invalidateOverlay(bool renderTwice);
|
||||
|
||||
struct OverlayGraph;
|
||||
typedef struct OverlayGraph * GraphHandle;
|
||||
typedef const char * (*GraphFormatFn)(const char * name,
|
||||
float min, float max, float avg, float freq, float last);
|
||||
|
||||
GraphHandle app_registerGraph(const char * name, RingBuffer buffer, float min, float max);
|
||||
GraphHandle app_registerGraph(const char * name, RingBuffer buffer,
|
||||
float min, float max, GraphFormatFn formatFn);
|
||||
void app_unregisterGraph(GraphHandle handle);
|
||||
void app_invalidateGraph(GraphHandle handle);
|
||||
|
||||
void app_overlayConfigRegister(const char * title,
|
||||
void (*callback)(void * udata, int * id), void * udata);
|
||||
@@ -128,9 +138,20 @@ void app_clipboardRequest(const LG_ClipboardReplyFn replyFn, void * opaque);
|
||||
*/
|
||||
void app_alert(LG_MsgAlert type, const char * fmt, ...);
|
||||
|
||||
typedef struct MsgBoxHandle * MsgBoxHandle;
|
||||
MsgBoxHandle app_msgBox(const char * caption, const char * fmt, ...);
|
||||
|
||||
typedef void (*MsgBoxConfirmCallback)(bool yes, void * opaque);
|
||||
MsgBoxHandle app_confirmMsgBox(const char * caption,
|
||||
MsgBoxConfirmCallback callback, void * opaque, const char * fmt, ...);
|
||||
|
||||
void app_msgBoxClose(MsgBoxHandle handle);
|
||||
|
||||
typedef struct KeybindHandle * KeybindHandle;
|
||||
typedef void (*KeybindFn)(int sc, void * opaque);
|
||||
|
||||
void app_showRecord(bool show);
|
||||
|
||||
/**
|
||||
* Register a handler for the <super>+<key> combination
|
||||
* @param sc The scancode to register
|
||||
@@ -152,4 +173,11 @@ void app_releaseKeybind(KeybindHandle * handle);
|
||||
*/
|
||||
void app_releaseAllKeybinds(void);
|
||||
|
||||
bool app_guestIsLinux(void);
|
||||
bool app_guestIsWindows(void);
|
||||
bool app_guestIsOSX(void);
|
||||
bool app_guestIsBSD(void);
|
||||
bool app_guestIsOther(void);
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
89
client/include/interface/audiodev.h
Normal file
89
client/include/interface/audiodev.h
Normal file
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2022 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_AUDIODEV_
|
||||
#define _H_I_AUDIODEV_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef int (*LG_AudioPullFn)(uint8_t * dst, int frames);
|
||||
typedef void (*LG_AudioPushFn)(uint8_t * src, int frames);
|
||||
|
||||
struct LG_AudioDevOps
|
||||
{
|
||||
/* internal name of the audio for debugging */
|
||||
const char * name;
|
||||
|
||||
/* called very early to allow for option registration, optional */
|
||||
void (*earlyInit)(void);
|
||||
|
||||
/* called to initialize the audio backend */
|
||||
bool (*init)(void);
|
||||
|
||||
/* final free */
|
||||
void (*free)(void);
|
||||
|
||||
struct
|
||||
{
|
||||
/* setup the stream for playback but don't start it yet
|
||||
* Note: the pull function returns f32 samples
|
||||
*/
|
||||
void (*setup)(int channels, int sampleRate, int requestedPeriodFrames,
|
||||
int * maxPeriodFrames, int * startFrames, LG_AudioPullFn pullFn);
|
||||
|
||||
/* called when there is data available to start playback */
|
||||
void (*start)(void);
|
||||
|
||||
/* called when SPICE reports the audio stream has stopped */
|
||||
void (*stop)(void);
|
||||
|
||||
/* [optional] called to set the volume of the channels */
|
||||
void (*volume)(int channels, const uint16_t volume[]);
|
||||
|
||||
/* [optional] called to set muting of the output */
|
||||
void (*mute)(bool mute);
|
||||
|
||||
/* return the current total playback latency in microseconds */
|
||||
uint64_t (*latency)(void);
|
||||
}
|
||||
playback;
|
||||
|
||||
struct
|
||||
{
|
||||
/* start the record stream
|
||||
* Note: currently SPICE only supports S16 samples so always assume so
|
||||
*/
|
||||
void (*start)(int channels, int sampleRate, LG_AudioPushFn pushFn);
|
||||
|
||||
/* called when SPICE reports the audio stream has stopped */
|
||||
void (*stop)(void);
|
||||
|
||||
/* [optional] called to set the volume of the channels */
|
||||
void (*volume)(int channels, const uint16_t volume[]);
|
||||
|
||||
/* [optional] called to set muting of the input */
|
||||
void (*mute)(bool mute);
|
||||
}
|
||||
record;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -123,13 +123,13 @@ struct LG_DisplayServerOps
|
||||
bool (*init)(const LG_DSInitParams params);
|
||||
|
||||
/* called at startup after window creation, renderer and SPICE is ready */
|
||||
void (*startup)();
|
||||
void (*startup)(void);
|
||||
|
||||
/* called just before final window destruction, before final free */
|
||||
void (*shutdown)();
|
||||
void (*shutdown)(void);
|
||||
|
||||
/* final free */
|
||||
void (*free)();
|
||||
void (*free)(void);
|
||||
|
||||
/*
|
||||
* return a system specific property, returns false if unsupported or failure
|
||||
@@ -170,14 +170,14 @@ struct LG_DisplayServerOps
|
||||
/* dm specific cursor implementations */
|
||||
void (*guestPointerUpdated)(double x, double y, double localX, double localY);
|
||||
void (*setPointer)(LG_DSPointer pointer);
|
||||
void (*grabKeyboard)();
|
||||
void (*ungrabKeyboard)();
|
||||
void (*grabKeyboard)(void);
|
||||
void (*ungrabKeyboard)(void);
|
||||
/* (un)grabPointer is used to toggle cursor tracking/confine in normal mode */
|
||||
void (*grabPointer)();
|
||||
void (*ungrabPointer)();
|
||||
void (*grabPointer)(void);
|
||||
void (*ungrabPointer)(void);
|
||||
/* (un)capturePointer is used do toggle special cursor tracking in capture mode */
|
||||
void (*capturePointer)();
|
||||
void (*uncapturePointer)();
|
||||
void (*capturePointer)(void);
|
||||
void (*uncapturePointer)(void);
|
||||
|
||||
/* exiting = true if the warp is to leave the window */
|
||||
void (*warpPointer)(int x, int y, bool exiting);
|
||||
@@ -185,14 +185,17 @@ struct LG_DisplayServerOps
|
||||
/* 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)();
|
||||
void (*realignPointer)(void);
|
||||
|
||||
/* 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)();
|
||||
void (*inhibitIdle)(void);
|
||||
void (*uninhibitIdle)(void);
|
||||
|
||||
/* called to request activation */
|
||||
void (*requestActivation)(void);
|
||||
|
||||
/* wait for the specified time without blocking UI processing/event loops */
|
||||
void (*wait)(unsigned int time);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -43,6 +43,10 @@ struct LG_OverlayOps
|
||||
* optional, if omitted assumes false */
|
||||
bool (*needs_render)(void * udata, bool interactive);
|
||||
|
||||
/* return true if the overlay currently requires overlay mode
|
||||
* optional, if omitted assumes false */
|
||||
bool (*needs_overlay)(void * udata);
|
||||
|
||||
/* perform the actual drawing/rendering
|
||||
*
|
||||
* `interactive` is true if the application is currently in overlay interaction
|
||||
@@ -59,6 +63,15 @@ struct LG_OverlayOps
|
||||
int (*render)(void * udata, bool interactive, struct Rect * windowRects,
|
||||
int maxRects);
|
||||
|
||||
/* called 25 times a second by the application
|
||||
*
|
||||
* Note: This may not run in the same context as `render`!
|
||||
*
|
||||
* return true if the frame needs to be rendered
|
||||
* optional, if omitted assumes false
|
||||
*/
|
||||
bool (*tick)(void * udata, unsigned long long tickCount);
|
||||
|
||||
/* TODO: add load/save settings capabillity */
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -66,9 +66,11 @@ LG_RendererRotate;
|
||||
|
||||
typedef struct LG_RendererFormat
|
||||
{
|
||||
FrameType type; // frame type
|
||||
unsigned int width; // image width
|
||||
unsigned int height; // image height
|
||||
FrameType type; // frame type
|
||||
unsigned int screenWidth; // actual width of the host
|
||||
unsigned int screenHeight; // actual height of the host
|
||||
unsigned int frameWidth; // width of frame transmitted
|
||||
unsigned int frameHeight; // height of frame transmitted
|
||||
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)
|
||||
@@ -139,8 +141,8 @@ typedef struct LG_RendererOps
|
||||
|
||||
/* called when the mouse has moved or changed visibillity
|
||||
* Context: cursorThread */
|
||||
bool (*onMouseEvent)(LG_Renderer * renderer, const bool visible, const int x,
|
||||
const int y);
|
||||
bool (*onMouseEvent)(LG_Renderer * renderer, const bool visible, int x, int y,
|
||||
const int hx, const int hy);
|
||||
|
||||
/* called when the frame format has changed
|
||||
* Context: frameThread */
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -24,12 +24,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include "common/types.h"
|
||||
|
||||
#define min(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; })
|
||||
#define max(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a > _b ? _a : _b; })
|
||||
|
||||
#define UPCAST(type, x) \
|
||||
(type *)((uintptr_t)(x) - offsetof(type, base))
|
||||
#include "common/util.h"
|
||||
|
||||
// reads the specified file into a new buffer
|
||||
// the callee must free the buffer
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -42,6 +42,7 @@ struct CursorTex
|
||||
struct EGL_Texture * texture;
|
||||
struct EGL_Shader * shader;
|
||||
GLuint uMousePos;
|
||||
GLuint uScale;
|
||||
GLuint uRotate;
|
||||
GLuint uCBMode;
|
||||
};
|
||||
@@ -73,7 +74,9 @@ struct EGL_Cursor
|
||||
int cbMode;
|
||||
|
||||
_Atomic(struct CursorPos) pos;
|
||||
_Atomic(struct CursorPos) hs;
|
||||
_Atomic(struct CursorSize) size;
|
||||
_Atomic(float) scale;
|
||||
|
||||
struct CursorTex norm;
|
||||
struct CursorTex mono;
|
||||
@@ -103,28 +106,22 @@ static bool cursorTexInit(struct CursorTex * t,
|
||||
return false;
|
||||
}
|
||||
|
||||
t->uMousePos = egl_shaderGetUniform(t->shader, "mouse" );
|
||||
t->uRotate = egl_shaderGetUniform(t->shader, "rotate");
|
||||
t->uCBMode = egl_shaderGetUniform(t->shader, "cbMode");
|
||||
t->uMousePos = egl_shaderGetUniform(t->shader, "mouse" );
|
||||
t->uScale = egl_shaderGetUniform(t->shader, "scale" );
|
||||
t->uRotate = egl_shaderGetUniform(t->shader, "rotate" );
|
||||
t->uCBMode = egl_shaderGetUniform(t->shader, "cbMode" );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void setCursorTexUniforms(EGL_Cursor * cursor,
|
||||
struct CursorTex * t, bool mono, float x, float y, float w, float h)
|
||||
struct CursorTex * t, bool mono, float x, float y,
|
||||
float w, float h, float scale)
|
||||
{
|
||||
if (mono)
|
||||
{
|
||||
glUniform4f(t->uMousePos, x, y, w, h / 2);
|
||||
glUniform1i(t->uRotate , cursor->rotate);
|
||||
glUniform1i(t->uCBMode , cursor->cbMode);
|
||||
}
|
||||
else
|
||||
{
|
||||
glUniform4f(t->uMousePos, x, y, w, h);
|
||||
glUniform1i(t->uRotate , cursor->rotate);
|
||||
glUniform1i(t->uCBMode , cursor->cbMode);
|
||||
}
|
||||
glUniform4f(t->uMousePos, x, y, w, mono ? h / 2 : h);
|
||||
glUniform1f(t->uScale , scale);
|
||||
glUniform1i(t->uRotate , cursor->rotate);
|
||||
glUniform1i(t->uCBMode , cursor->cbMode);
|
||||
}
|
||||
|
||||
static void cursorTexFree(struct CursorTex * t)
|
||||
@@ -166,9 +163,12 @@ bool egl_cursorInit(EGL_Cursor ** cursor)
|
||||
(*cursor)->cbMode = option_get_int("egl", "cbMode");
|
||||
|
||||
struct CursorPos pos = { .x = 0, .y = 0 };
|
||||
struct CursorPos hs = { .x = 0, .y = 0 };
|
||||
struct CursorSize size = { .w = 0, .h = 0 };
|
||||
atomic_init(&(*cursor)->pos, pos);
|
||||
atomic_init(&(*cursor)->size, size);
|
||||
atomic_init(&(*cursor)->pos , pos );
|
||||
atomic_init(&(*cursor)->hs , hs );
|
||||
atomic_init(&(*cursor)->size , size);
|
||||
atomic_init(&(*cursor)->scale, 1.0f);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -229,11 +229,19 @@ void egl_cursorSetSize(EGL_Cursor * cursor, const float w, const float h)
|
||||
atomic_store(&cursor->size, size);
|
||||
}
|
||||
|
||||
void egl_cursorSetState(EGL_Cursor * cursor, const bool visible, const float x, const float y)
|
||||
void egl_cursorSetScale(EGL_Cursor * cursor, const float scale)
|
||||
{
|
||||
atomic_store(&cursor->scale, scale);
|
||||
}
|
||||
|
||||
void egl_cursorSetState(EGL_Cursor * cursor, const bool visible,
|
||||
const float x, const float y, const float hx, const float hy)
|
||||
{
|
||||
cursor->visible = visible;
|
||||
struct CursorPos pos = { .x = x, .y = y};
|
||||
struct CursorPos pos = { .x = x , .y = y };
|
||||
struct CursorPos hs = { .x = hx, .y = hy };
|
||||
atomic_store(&cursor->pos, pos);
|
||||
atomic_store(&cursor->hs , hs);
|
||||
}
|
||||
|
||||
struct CursorState egl_cursorRender(EGL_Cursor * cursor,
|
||||
@@ -252,22 +260,43 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor,
|
||||
switch(cursor->type)
|
||||
{
|
||||
case LG_CURSOR_MASKED_COLOR:
|
||||
// fall through
|
||||
{
|
||||
uint32_t xor[cursor->height][cursor->width];
|
||||
for(int y = 0; y < cursor->height; ++y)
|
||||
for(int x = 0; x < cursor->width; ++x)
|
||||
{
|
||||
uint32_t * src = (uint32_t *)(data + (cursor->stride * y) + x * 4);
|
||||
const bool masked = (*src & 0xFF000000) != 0;
|
||||
if (masked)
|
||||
*src = xor[y][x] = *src & 0x00FFFFFF;
|
||||
else
|
||||
{
|
||||
xor[y][x] = 0xFF000000;
|
||||
*src |= 0xFF000000;
|
||||
}
|
||||
}
|
||||
|
||||
egl_textureSetup(cursor->mono.texture, EGL_PF_BGRA,
|
||||
cursor->width, cursor->height, sizeof(xor[0]));
|
||||
egl_textureUpdate(cursor->mono.texture, (uint8_t *)xor);
|
||||
}
|
||||
// fall through
|
||||
|
||||
case LG_CURSOR_COLOR:
|
||||
{
|
||||
egl_textureSetup(cursor->norm.texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->stride);
|
||||
egl_textureSetup(cursor->norm.texture, EGL_PF_BGRA,
|
||||
cursor->width, cursor->height, cursor->stride);
|
||||
egl_textureUpdate(cursor->norm.texture, data);
|
||||
egl_modelSetTexture(cursor->model, cursor->norm.texture);
|
||||
break;
|
||||
}
|
||||
|
||||
case LG_CURSOR_MONOCHROME:
|
||||
{
|
||||
uint32_t and[cursor->width * cursor->height];
|
||||
uint32_t xor[cursor->width * cursor->height];
|
||||
uint32_t and[cursor->height][cursor->width];
|
||||
uint32_t xor[cursor->height][cursor->width];
|
||||
|
||||
for(int y = 0; y < cursor->height; ++y)
|
||||
{
|
||||
for(int x = 0; x < cursor->width; ++x)
|
||||
{
|
||||
const uint8_t * srcAnd = data + (cursor->stride * y) + (x / 8);
|
||||
@@ -276,12 +305,15 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor,
|
||||
const uint32_t andMask = (*srcAnd & mask) ? 0xFFFFFFFF : 0xFF000000;
|
||||
const uint32_t xorMask = (*srcXor & mask) ? 0x00FFFFFF : 0x00000000;
|
||||
|
||||
and[y * cursor->width + x] = andMask;
|
||||
xor[y * cursor->width + x] = xorMask;
|
||||
and[y][x] = andMask;
|
||||
xor[y][x] = xorMask;
|
||||
}
|
||||
}
|
||||
|
||||
egl_textureSetup (cursor->norm.texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->width * 4);
|
||||
egl_textureSetup (cursor->mono.texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->width * 4);
|
||||
egl_textureSetup(cursor->norm.texture, EGL_PF_BGRA,
|
||||
cursor->width, cursor->height, sizeof(and[0]));
|
||||
egl_textureSetup(cursor->mono.texture, EGL_PF_BGRA,
|
||||
cursor->width, cursor->height, sizeof(xor[0]));
|
||||
egl_textureUpdate(cursor->norm.texture, (uint8_t *)and);
|
||||
egl_textureUpdate(cursor->mono.texture, (uint8_t *)xor);
|
||||
break;
|
||||
@@ -292,8 +324,15 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor,
|
||||
|
||||
cursor->rotate = rotate;
|
||||
|
||||
struct CursorPos pos = atomic_load(&cursor->pos);
|
||||
struct CursorSize size = atomic_load(&cursor->size);
|
||||
struct CursorPos pos = atomic_load(&cursor->pos );
|
||||
float scale = atomic_load(&cursor->scale);
|
||||
struct CursorPos hs = atomic_load(&cursor->hs );
|
||||
struct CursorSize size = atomic_load(&cursor->size );
|
||||
|
||||
pos.x -= hs.x * scale;
|
||||
pos.y -= hs.y * scale;
|
||||
size.w *= scale;
|
||||
size.h *= scale;
|
||||
|
||||
struct CursorState state = {
|
||||
.visible = true,
|
||||
@@ -342,13 +381,33 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor,
|
||||
case LG_CURSOR_MONOCHROME:
|
||||
{
|
||||
egl_shaderUse(cursor->norm.shader);
|
||||
setCursorTexUniforms(cursor, &cursor->norm, true, pos.x, pos.y, size.w, size.h);
|
||||
setCursorTexUniforms(cursor, &cursor->norm, true, pos.x, pos.y,
|
||||
size.w, size.h, scale);
|
||||
glBlendFunc(GL_ZERO, GL_SRC_COLOR);
|
||||
egl_modelSetTexture(cursor->model, cursor->norm.texture);
|
||||
egl_modelRender(cursor->model);
|
||||
|
||||
egl_shaderUse(cursor->mono.shader);
|
||||
setCursorTexUniforms(cursor, &cursor->mono, true, pos.x, pos.y, size.w, size.h);
|
||||
setCursorTexUniforms(cursor, &cursor->mono, true, pos.x, pos.y,
|
||||
size.w, size.h, scale);
|
||||
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
|
||||
egl_modelSetTexture(cursor->model, cursor->mono.texture);
|
||||
egl_modelRender(cursor->model);
|
||||
break;
|
||||
}
|
||||
|
||||
case LG_CURSOR_MASKED_COLOR:
|
||||
{
|
||||
egl_shaderUse(cursor->norm.shader);
|
||||
setCursorTexUniforms(cursor, &cursor->norm, false, pos.x, pos.y,
|
||||
size.w, size.h, scale);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
egl_modelSetTexture(cursor->model, cursor->norm.texture);
|
||||
egl_modelRender(cursor->model);
|
||||
|
||||
egl_shaderUse(cursor->mono.shader);
|
||||
setCursorTexUniforms(cursor, &cursor->mono, false, pos.x, pos.y,
|
||||
size.w, size.h, scale);
|
||||
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
|
||||
egl_modelSetTexture(cursor->model, cursor->mono.texture);
|
||||
egl_modelRender(cursor->model);
|
||||
@@ -358,17 +417,10 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor,
|
||||
case LG_CURSOR_COLOR:
|
||||
{
|
||||
egl_shaderUse(cursor->norm.shader);
|
||||
setCursorTexUniforms(cursor, &cursor->norm, false, pos.x, pos.y, size.w, size.h);
|
||||
setCursorTexUniforms(cursor, &cursor->norm, false, pos.x, pos.y,
|
||||
size.w, size.h, scale);
|
||||
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
egl_modelRender(cursor->model);
|
||||
break;
|
||||
}
|
||||
|
||||
case LG_CURSOR_MASKED_COLOR:
|
||||
{
|
||||
egl_shaderUse(cursor->mono.shader);
|
||||
setCursorTexUniforms(cursor, &cursor->mono, false, pos.x, pos.y, size.w, size.h);
|
||||
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
|
||||
egl_modelSetTexture(cursor->model, cursor->norm.texture);
|
||||
egl_modelRender(cursor->model);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -45,8 +45,10 @@ bool egl_cursorSetShape(
|
||||
|
||||
void egl_cursorSetSize(EGL_Cursor * cursor, const float x, const float y);
|
||||
|
||||
void egl_cursorSetScale(EGL_Cursor * cursor, const float scale);
|
||||
|
||||
void egl_cursorSetState(EGL_Cursor * cursor, const bool visible,
|
||||
const float x, const float y);
|
||||
const float x, const float y, const float hx, const float hy);
|
||||
|
||||
struct CursorState egl_cursorRender(EGL_Cursor * cursor,
|
||||
LG_RendererRotate rotate, int width, int height);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -57,7 +57,6 @@ struct EGL_Desktop
|
||||
EGLDisplay * display;
|
||||
|
||||
EGL_Texture * texture;
|
||||
GLuint sampler;
|
||||
struct DesktopShader shader;
|
||||
EGL_DesktopRects * mesh;
|
||||
CountedBuffer * matrix;
|
||||
@@ -228,7 +227,8 @@ void egl_desktopConfigUI(EGL_Desktop * desktop)
|
||||
for (int i = 0; i < EGL_SCALE_MAX; ++i)
|
||||
{
|
||||
bool selected = i == desktop->scaleAlgo;
|
||||
if (igSelectableBool(algorithmNames[i], selected, 0, (ImVec2) { 0.0f, 0.0f }))
|
||||
if (igSelectable_Bool(algorithmNames[i], selected, 0,
|
||||
(ImVec2) { 0.0f, 0.0f }))
|
||||
desktop->scaleAlgo = i;
|
||||
if (selected)
|
||||
igSetItemDefaultFocus();
|
||||
@@ -280,14 +280,14 @@ bool egl_desktopSetup(EGL_Desktop * desktop, const LG_RendererFormat format)
|
||||
return false;
|
||||
}
|
||||
|
||||
desktop->width = format.width;
|
||||
desktop->height = format.height;
|
||||
desktop->width = format.frameWidth;
|
||||
desktop->height = format.frameHeight;
|
||||
|
||||
if (!egl_textureSetup(
|
||||
desktop->texture,
|
||||
pixFmt,
|
||||
format.width,
|
||||
format.height,
|
||||
format.frameWidth,
|
||||
format.frameHeight,
|
||||
format.pitch
|
||||
))
|
||||
{
|
||||
@@ -295,12 +295,6 @@ bool egl_desktopSetup(EGL_Desktop * desktop, const LG_RendererFormat format)
|
||||
return false;
|
||||
}
|
||||
|
||||
glGenSamplers(1, &desktop->sampler);
|
||||
glSamplerParameteri(desktop->sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glSamplerParameteri(desktop->sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glSamplerParameteri(desktop->sampler, GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE);
|
||||
glSamplerParameteri(desktop->sampler, GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -395,7 +389,7 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth,
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glBindSampler(0, desktop->sampler);
|
||||
glBindSampler(0, desktop->texture->sampler);
|
||||
|
||||
if (finalSizeX > desktop->width || finalSizeY > desktop->height)
|
||||
scaleType = EGL_DESKTOP_DOWNSCALE;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -33,12 +33,12 @@ void egl_drawTorus(EGL_Model * model, unsigned int pts, float x, float y,
|
||||
const float angle = (i / (float)pts) * M_PI * 2.0f;
|
||||
const float c = cos(angle);
|
||||
const float s = sin(angle);
|
||||
*dst = x + (inner * c); ++dst;
|
||||
*dst = y + (inner * s); ++dst;
|
||||
*dst = 0.0f; ++dst;
|
||||
*dst = x + (outer * c); ++dst;
|
||||
*dst = y + (outer * s); ++dst;
|
||||
*dst = 0.0f; ++dst;
|
||||
*dst++ = x + (inner * c);
|
||||
*dst++ = y + (inner * s);
|
||||
*dst++ = 0.0f;
|
||||
*dst++ = x + (outer * c);
|
||||
*dst++ = y + (outer * s);
|
||||
*dst++ = 0.0f;
|
||||
}
|
||||
|
||||
egl_modelAddVerts(model, v, NULL, (pts + 1) * 2);
|
||||
@@ -56,12 +56,12 @@ void egl_drawTorusArc(EGL_Model * model, unsigned int pts, float x, float y,
|
||||
const float angle = s + ((i / (float)pts) * e);
|
||||
const float c = cos(angle);
|
||||
const float s = sin(angle);
|
||||
*dst = x + (inner * c); ++dst;
|
||||
*dst = y + (inner * s); ++dst;
|
||||
*dst = 0.0f; ++dst;
|
||||
*dst = x + (outer * c); ++dst;
|
||||
*dst = y + (outer * s); ++dst;
|
||||
*dst = 0.0f; ++dst;
|
||||
*dst++ = x + (inner * c);
|
||||
*dst++ = y + (inner * s);
|
||||
*dst++ = 0.0f;
|
||||
*dst++ = x + (outer * c);
|
||||
*dst++ = y + (outer * s);
|
||||
*dst++ = 0.0f;
|
||||
}
|
||||
|
||||
egl_modelAddVerts(model, v, NULL, (pts + 1) * 2);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -39,7 +39,6 @@
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "app.h"
|
||||
#include "egl_dynprocs.h"
|
||||
#include "model.h"
|
||||
#include "shader.h"
|
||||
@@ -103,9 +102,11 @@ struct Inst
|
||||
|
||||
bool cursorVisible;
|
||||
int cursorX , cursorY;
|
||||
int cursorHX , cursorHY;
|
||||
float mouseWidth , mouseHeight;
|
||||
float mouseScaleX, mouseScaleY;
|
||||
bool showDamage;
|
||||
bool scalePointer;
|
||||
|
||||
struct CursorState cursorLast;
|
||||
|
||||
@@ -197,6 +198,13 @@ static struct Option egl_options[] =
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false
|
||||
},
|
||||
{
|
||||
.module = "egl",
|
||||
.name = "scalePointer",
|
||||
.description = "Keep the pointer size 1:1 when downscaling",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true
|
||||
},
|
||||
|
||||
{0}
|
||||
};
|
||||
@@ -249,7 +257,8 @@ static bool egl_create(LG_Renderer ** renderer, const LG_RendererParams params,
|
||||
this->desktopDamage[0].count = -1;
|
||||
|
||||
this->importTimings = ringbuffer_new(256, sizeof(float));
|
||||
this->importGraph = app_registerGraph("IMPORT", this->importTimings, 0.0f, 5.0f);
|
||||
this->importGraph = app_registerGraph("IMPORT", this->importTimings,
|
||||
0.0f, 5.0f, NULL);
|
||||
|
||||
*needsOpenGL = false;
|
||||
return true;
|
||||
@@ -331,18 +340,18 @@ static void egl_calc_mouse_size(struct Inst * this)
|
||||
{
|
||||
case LG_ROTATE_0:
|
||||
case LG_ROTATE_180:
|
||||
this->mouseScaleX = 2.0f / this->format.width;
|
||||
this->mouseScaleY = 2.0f / this->format.height;
|
||||
w = this->format.width;
|
||||
h = this->format.height;
|
||||
this->mouseScaleX = 2.0f / this->format.screenWidth;
|
||||
this->mouseScaleY = 2.0f / this->format.screenHeight;
|
||||
w = this->format.screenWidth;
|
||||
h = this->format.screenHeight;
|
||||
break;
|
||||
|
||||
case LG_ROTATE_90:
|
||||
case LG_ROTATE_270:
|
||||
this->mouseScaleX = 2.0f / this->format.height;
|
||||
this->mouseScaleY = 2.0f / this->format.width;
|
||||
w = this->format.height;
|
||||
h = this->format.width;
|
||||
this->mouseScaleX = 2.0f / this->format.screenHeight;
|
||||
this->mouseScaleY = 2.0f / this->format.screenWidth;
|
||||
w = this->format.screenHeight;
|
||||
h = this->format.screenWidth;
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -381,8 +390,10 @@ static void egl_calc_mouse_state(struct Inst * this)
|
||||
egl_cursorSetState(
|
||||
this->cursor,
|
||||
this->cursorVisible,
|
||||
(((float)this->cursorX * this->mouseScaleX) - 1.0f) * this->scaleX,
|
||||
(((float)this->cursorY * this->mouseScaleY) - 1.0f) * this->scaleY
|
||||
(((float)this->cursorX * this->mouseScaleX) - 1.0f) * this->scaleX,
|
||||
(((float)this->cursorY * this->mouseScaleY) - 1.0f) * this->scaleY,
|
||||
((float)this->cursorHX * this->mouseScaleX) * this->scaleX,
|
||||
((float)this->cursorHY * this->mouseScaleY) * this->scaleY
|
||||
);
|
||||
break;
|
||||
|
||||
@@ -391,8 +402,10 @@ static void egl_calc_mouse_state(struct Inst * this)
|
||||
egl_cursorSetState(
|
||||
this->cursor,
|
||||
this->cursorVisible,
|
||||
(((float)this->cursorX * this->mouseScaleX) - 1.0f) * this->scaleY,
|
||||
(((float)this->cursorY * this->mouseScaleY) - 1.0f) * this->scaleX
|
||||
(((float)this->cursorX * this->mouseScaleX) - 1.0f) * this->scaleY,
|
||||
(((float)this->cursorY * this->mouseScaleY) - 1.0f) * this->scaleX,
|
||||
((float)this->cursorHX * this->mouseScaleX) * this->scaleY,
|
||||
((float)this->cursorHY * this->mouseScaleY) * this->scaleX
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -406,14 +419,14 @@ static void egl_update_scale_type(struct Inst * this)
|
||||
{
|
||||
case LG_ROTATE_0:
|
||||
case LG_ROTATE_180:
|
||||
width = this->format.width;
|
||||
height = this->format.height;
|
||||
width = this->format.frameWidth;
|
||||
height = this->format.frameHeight;
|
||||
break;
|
||||
|
||||
case LG_ROTATE_90:
|
||||
case LG_ROTATE_270:
|
||||
width = this->format.height;
|
||||
height = this->format.width;
|
||||
width = this->format.frameHeight;
|
||||
height = this->format.frameWidth;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -465,6 +478,16 @@ static void egl_onResize(LG_Renderer * renderer, const int width, const int heig
|
||||
this->screenScaleY = 1.0f / this->height;
|
||||
|
||||
egl_calc_mouse_state(this);
|
||||
if (this->scalePointer)
|
||||
{
|
||||
float scale = max(1.0f,
|
||||
this->formatValid ?
|
||||
max(
|
||||
(float)this->format.screenWidth / this->width,
|
||||
(float)this->format.screenHeight / this->height)
|
||||
: 1.0f);
|
||||
egl_cursorSetScale(this->cursor, scale);
|
||||
}
|
||||
|
||||
INTERLOCKED_SECTION(this->desktopDamageLock, {
|
||||
this->desktopDamage[this->desktopDamageIdx].count = -1;
|
||||
@@ -472,6 +495,7 @@ static void egl_onResize(LG_Renderer * renderer, const int width, const int heig
|
||||
|
||||
// this is needed to refresh the font atlas texture
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplOpenGL3_Init("#version 300 es");
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
|
||||
egl_damageResize(this->damage, this->translateX, this->translateY, this->scaleX, this->scaleY);
|
||||
@@ -497,12 +521,15 @@ static bool egl_onMouseShape(LG_Renderer * renderer, const LG_RendererCursor cur
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool egl_onMouseEvent(LG_Renderer * renderer, const bool visible, const int x, const int y)
|
||||
static bool egl_onMouseEvent(LG_Renderer * renderer, const bool visible,
|
||||
int x, int y, const int hx, const int hy)
|
||||
{
|
||||
struct Inst * this = UPCAST(struct Inst, renderer);
|
||||
this->cursorVisible = visible;
|
||||
this->cursorX = x;
|
||||
this->cursorY = y;
|
||||
this->cursorX = x + hx;
|
||||
this->cursorY = y + hy;
|
||||
this->cursorHX = hx;
|
||||
this->cursorHY = hy;
|
||||
egl_calc_mouse_state(this);
|
||||
return true;
|
||||
}
|
||||
@@ -534,8 +561,14 @@ static bool egl_onFrameFormat(LG_Renderer * renderer, const LG_RendererFormat fo
|
||||
}
|
||||
}
|
||||
|
||||
if (this->scalePointer)
|
||||
{
|
||||
float scale = max(1.0f, (float)format.screenWidth / this->width);
|
||||
egl_cursorSetScale(this->cursor, scale);
|
||||
}
|
||||
|
||||
egl_update_scale_type(this);
|
||||
egl_damageSetup(this->damage, format.width, format.height);
|
||||
egl_damageSetup(this->damage, format.frameWidth, format.frameHeight);
|
||||
|
||||
/* we need full screen damage when the format changes */
|
||||
INTERLOCKED_SECTION(this->desktopDamageLock, {
|
||||
@@ -736,7 +769,13 @@ static bool egl_renderStartup(LG_Renderer * renderer, bool useDMA)
|
||||
}
|
||||
|
||||
const char * client_exts = eglQueryString(this->display, EGL_EXTENSIONS);
|
||||
bool debugContext = option_get_bool("egl", "debug");
|
||||
if (!client_exts)
|
||||
{
|
||||
DEBUG_ERROR("Failed to query EGL_EXTENSIONS");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool debug = option_get_bool("egl", "debug");
|
||||
EGLint ctxattr[5];
|
||||
int ctxidx = 0;
|
||||
|
||||
@@ -746,17 +785,17 @@ static bool egl_renderStartup(LG_Renderer * renderer, bool useDMA)
|
||||
if (maj > 1 || (maj == 1 && min >= 5))
|
||||
{
|
||||
ctxattr[ctxidx++] = EGL_CONTEXT_OPENGL_DEBUG;
|
||||
ctxattr[ctxidx++] = debugContext ? EGL_TRUE : EGL_FALSE;
|
||||
ctxattr[ctxidx++] = debug ? EGL_TRUE : EGL_FALSE;
|
||||
}
|
||||
else if (util_hasGLExt(client_exts, "EGL_KHR_create_context"))
|
||||
{
|
||||
ctxattr[ctxidx++] = EGL_CONTEXT_FLAGS_KHR;
|
||||
ctxattr[ctxidx++] = debugContext ? EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR : 0;
|
||||
ctxattr[ctxidx++] = debug ? EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR : 0;
|
||||
}
|
||||
else if (debugContext)
|
||||
else if (debug)
|
||||
DEBUG_WARN("Cannot create debug contexts before EGL 1.5 without EGL_KHR_create_context");
|
||||
|
||||
ctxattr[ctxidx++] = EGL_NONE;
|
||||
ctxattr[ctxidx] = EGL_NONE;
|
||||
|
||||
this->context = eglCreateContext(this->display, this->configs, EGL_NO_CONTEXT, ctxattr);
|
||||
if (this->context == EGL_NO_CONTEXT)
|
||||
@@ -784,15 +823,30 @@ static bool egl_renderStartup(LG_Renderer * renderer, bool useDMA)
|
||||
|
||||
eglMakeCurrent(this->display, this->surface, this->surface, this->context);
|
||||
const char * gl_exts = (const char *)glGetString(GL_EXTENSIONS);
|
||||
if (!gl_exts)
|
||||
{
|
||||
DEBUG_ERROR("Failed to query GL_EXTENSIONS");
|
||||
return false;
|
||||
}
|
||||
|
||||
const char * vendor = (const char *)glGetString(GL_VENDOR);
|
||||
if (!vendor)
|
||||
{
|
||||
DEBUG_ERROR("Failed to query GL_VENDOR");
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_INFO("EGL : %d.%d", maj, min);
|
||||
DEBUG_INFO("Vendor : %s", vendor);
|
||||
DEBUG_INFO("Renderer: %s", glGetString(GL_RENDERER));
|
||||
DEBUG_INFO("Version : %s", glGetString(GL_VERSION ));
|
||||
DEBUG_INFO("EGL APIs: %s", eglQueryString(this->display, EGL_CLIENT_APIS));
|
||||
DEBUG_INFO("EGL Exts: %s", client_exts);
|
||||
DEBUG_INFO("GL Exts : %s", gl_exts);
|
||||
|
||||
if (debug)
|
||||
{
|
||||
DEBUG_INFO("EGL Exts: %s", client_exts);
|
||||
DEBUG_INFO("GL Exts : %s", gl_exts);
|
||||
}
|
||||
|
||||
GLint esMaj, esMin;
|
||||
glGetIntegerv(GL_MAJOR_VERSION, &esMaj);
|
||||
@@ -818,6 +872,8 @@ static bool egl_renderStartup(LG_Renderer * renderer, bool useDMA)
|
||||
if (this->noSwapDamage)
|
||||
DEBUG_WARN("egl:noSwapDamage specified, disabling swap buffers with damage.");
|
||||
|
||||
this->scalePointer = option_get_bool("egl", "scalePointer");
|
||||
|
||||
if (!g_egl_dynProcs.glEGLImageTargetTexture2DOES)
|
||||
DEBUG_INFO("glEGLImageTargetTexture2DOES unavilable, DMA support disabled");
|
||||
else if (!g_egl_dynProcs.eglCreateImage || !g_egl_dynProcs.eglDestroyImage)
|
||||
@@ -839,7 +895,7 @@ static bool egl_renderStartup(LG_Renderer * renderer, bool useDMA)
|
||||
}
|
||||
}
|
||||
|
||||
if (debugContext)
|
||||
if (debug)
|
||||
{
|
||||
if ((esMaj > 3 || (esMaj == 3 && esMin >= 2)) && g_egl_dynProcs.glDebugMessageCallback)
|
||||
{
|
||||
@@ -989,8 +1045,8 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
|
||||
int y = rect->y > 0 ? rect->y - 1 : 0;
|
||||
accumulated->rects[accumulated->count++] = (struct FrameDamageRect) {
|
||||
.x = x, .y = y,
|
||||
.width = min(this->format.width - x, rect->width + 2),
|
||||
.height = min(this->format.height - y, rect->height + 2),
|
||||
.width = min(this->format.frameWidth - x, rect->width + 2),
|
||||
.height = min(this->format.frameHeight - y, rect->height + 2),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1003,7 +1059,8 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
|
||||
if (!renderAll)
|
||||
{
|
||||
double matrix[6];
|
||||
egl_screenToDesktopMatrix(matrix, this->format.width, this->format.height,
|
||||
egl_screenToDesktopMatrix(matrix,
|
||||
this->format.frameWidth, this->format.frameHeight,
|
||||
this->translateX, this->translateY, this->scaleX, this->scaleY, rotate,
|
||||
this->width, this->height);
|
||||
|
||||
@@ -1022,7 +1079,7 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
|
||||
for (int j = 0; j < count; ++j)
|
||||
accumulated->count += egl_screenToDesktop(
|
||||
accumulated->rects + accumulated->count, matrix, damage + j,
|
||||
this->format.width, this->format.height
|
||||
this->format.frameWidth, this->format.frameHeight
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1130,7 +1187,8 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
|
||||
else
|
||||
{
|
||||
double matrix[6];
|
||||
egl_desktopToScreenMatrix(matrix, this->format.width, this->format.height,
|
||||
egl_desktopToScreenMatrix(matrix,
|
||||
this->format.frameWidth, this->format.frameHeight,
|
||||
this->translateX, this->translateY, this->scaleX, this->scaleY, rotate,
|
||||
this->width, this->height);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -268,7 +268,7 @@ static bool egl_filterDownscaleImguiConfig(EGL_Filter * filter)
|
||||
for (int i = 0; i < DOWNSCALE_COUNT; ++i)
|
||||
{
|
||||
bool selected = i == this->filter;
|
||||
if (igSelectableBool(filterNames[i], selected, 0, (ImVec2) { 0.0f, 0.0f }))
|
||||
if (igSelectable_Bool(filterNames[i], selected, 0, (ImVec2) { 0.0f, 0.0f }))
|
||||
{
|
||||
redraw = true;
|
||||
this->filter = i;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -23,7 +23,7 @@
|
||||
#include "texture.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "ll.h"
|
||||
#include "common/ll.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -124,10 +124,31 @@ void egl_modelSetDefault(EGL_Model * model, bool flipped)
|
||||
void egl_modelAddVerts(EGL_Model * model, const GLfloat * verticies, const GLfloat * uvs, const size_t count)
|
||||
{
|
||||
struct FloatList * fl = malloc(sizeof(*fl));
|
||||
if (!fl)
|
||||
{
|
||||
DEBUG_ERROR("out of memory");
|
||||
return;
|
||||
}
|
||||
|
||||
fl->count = count;
|
||||
fl->v = malloc(sizeof(GLfloat) * count * 3);
|
||||
fl->u = malloc(sizeof(GLfloat) * count * 2);
|
||||
|
||||
fl->v = malloc(sizeof(GLfloat) * count * 3);
|
||||
if (!fl->v)
|
||||
{
|
||||
DEBUG_ERROR("out of memory");
|
||||
free(fl);
|
||||
return;
|
||||
}
|
||||
|
||||
fl->u = malloc(sizeof(GLfloat) * count * 2);
|
||||
if (!fl->u)
|
||||
{
|
||||
DEBUG_ERROR("out of memory");
|
||||
free(fl->v);
|
||||
free(fl);
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(fl->v, verticies, sizeof(GLfloat) * count * 3);
|
||||
|
||||
if (uvs)
|
||||
@@ -164,18 +185,20 @@ void egl_modelRender(EGL_Model * model)
|
||||
|
||||
/* buffer the verticies */
|
||||
struct FloatList * fl;
|
||||
for(ll_reset(model->verticies); ll_walk(model->verticies, (void **)&fl);)
|
||||
ll_lock(model->verticies);
|
||||
ll_forEachNL(model->verticies, item, fl)
|
||||
{
|
||||
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(GLfloat) * fl->count * 3, fl->v);
|
||||
offset += sizeof(GLfloat) * fl->count * 3;
|
||||
}
|
||||
|
||||
/* buffer the uvs */
|
||||
for(ll_reset(model->verticies); ll_walk(model->verticies, (void **)&fl);)
|
||||
ll_forEachNL(model->verticies, item, fl)
|
||||
{
|
||||
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(GLfloat) * fl->count * 2, fl->u);
|
||||
offset += sizeof(GLfloat) * fl->count * 2;
|
||||
}
|
||||
ll_unlock(model->verticies);
|
||||
|
||||
/* set up vertex arrays in the VAO */
|
||||
glEnableVertexAttribArray(0);
|
||||
@@ -199,11 +222,13 @@ void egl_modelRender(EGL_Model * model)
|
||||
/* draw the arrays */
|
||||
GLint offset = 0;
|
||||
struct FloatList * fl;
|
||||
for(ll_reset(model->verticies); ll_walk(model->verticies, (void **)&fl);)
|
||||
ll_lock(model->verticies);
|
||||
ll_forEachNL(model->verticies, item, fl)
|
||||
{
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, offset, fl->count);
|
||||
offset += fl->count;
|
||||
}
|
||||
ll_unlock(model->verticies);
|
||||
|
||||
/* unbind and cleanup */
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -74,6 +74,12 @@ void egl_postProcessEarlyInit(void)
|
||||
.type = OPTION_TYPE_STRING,
|
||||
.value.x_string = ""
|
||||
},
|
||||
{
|
||||
.module = "egl",
|
||||
.name = "preset",
|
||||
.description = "The initial filter preset to load",
|
||||
.type = OPTION_TYPE_STRING
|
||||
},
|
||||
{ 0 }
|
||||
};
|
||||
option_register(options);
|
||||
@@ -82,6 +88,8 @@ void egl_postProcessEarlyInit(void)
|
||||
EGL_Filters[i]->earlyInit();
|
||||
}
|
||||
|
||||
static void loadPreset(struct EGL_PostProcess * this, const char * name);
|
||||
|
||||
static void loadPresetList(struct EGL_PostProcess * this)
|
||||
{
|
||||
DIR * dir = NULL;
|
||||
@@ -114,6 +122,9 @@ static void loadPresetList(struct EGL_PostProcess * this)
|
||||
}
|
||||
|
||||
struct dirent * entry;
|
||||
const char * preset = option_get_string("egl", "preset");
|
||||
this->activePreset = -1;
|
||||
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
if (entry->d_type != DT_REG)
|
||||
@@ -127,10 +138,21 @@ static void loadPresetList(struct EGL_PostProcess * this)
|
||||
goto fail;
|
||||
}
|
||||
stringlist_push(this->presets, name);
|
||||
|
||||
if (preset && strcmp(preset, name) == 0)
|
||||
this->activePreset = stringlist_count(this->presets) - 1;
|
||||
}
|
||||
closedir(dir);
|
||||
|
||||
this->activePreset = -1;
|
||||
|
||||
if (preset)
|
||||
{
|
||||
if (this->activePreset > -1)
|
||||
loadPreset(this, preset);
|
||||
else
|
||||
DEBUG_WARN("egl:preset '%s' does not exist", preset);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
fail:
|
||||
@@ -334,7 +356,8 @@ static bool presetsUI(struct EGL_PostProcess * this)
|
||||
for (unsigned i = 0; i < stringlist_count(this->presets); ++i)
|
||||
{
|
||||
bool selected = i == this->activePreset;
|
||||
if (igSelectableBool(stringlist_at(this->presets, i), selected, 0, (ImVec2) { 0.0f, 0.0f }))
|
||||
if (igSelectable_Bool(stringlist_at(this->presets, i), selected, 0,
|
||||
(ImVec2) { 0.0f, 0.0f }))
|
||||
{
|
||||
this->activePreset = i;
|
||||
redraw = true;
|
||||
@@ -365,7 +388,7 @@ static bool presetsUI(struct EGL_PostProcess * this)
|
||||
if (igButton("Save preset as...", (ImVec2) { 0.0f, 0.0f }))
|
||||
{
|
||||
this->presetEdit[0] = '\0';
|
||||
igOpenPopup("Save preset as...", ImGuiPopupFlags_None);
|
||||
igOpenPopup_Str("Save preset as...", ImGuiPopupFlags_None);
|
||||
}
|
||||
|
||||
igSameLine(0.0f, -1.0f);
|
||||
@@ -401,7 +424,7 @@ static bool presetsUI(struct EGL_PostProcess * this)
|
||||
}
|
||||
|
||||
if (this->presetError)
|
||||
igOpenPopup("Preset error", ImGuiPopupFlags_None);
|
||||
igOpenPopup_Str("Preset error", ImGuiPopupFlags_None);
|
||||
|
||||
if (igBeginPopupModal("Preset error", NULL, ImGuiWindowFlags_AlwaysAutoResize))
|
||||
{
|
||||
@@ -425,7 +448,7 @@ static bool presetsUI(struct EGL_PostProcess * this)
|
||||
|
||||
static void drawDropTarget(void)
|
||||
{
|
||||
igPushStyleColorVec4(ImGuiCol_Separator, (ImVec4) { 1.0f, 1.0f, 0.0f, 1.0f });
|
||||
igPushStyleColor_Vec4(ImGuiCol_Separator, (ImVec4) { 1.0f, 1.0f, 0.0f, 1.0f });
|
||||
igSeparator();
|
||||
igPopStyleColor(1);
|
||||
}
|
||||
@@ -456,8 +479,8 @@ static void configUI(void * opaque, int * id)
|
||||
if (moving && mouseIdx < moveIdx && i == mouseIdx)
|
||||
drawDropTarget();
|
||||
|
||||
igPushIDPtr(filter);
|
||||
bool draw = igCollapsingHeaderBoolPtr(filter->ops.name, NULL, 0);
|
||||
igPushID_Ptr(filter);
|
||||
bool draw = igCollapsingHeader_BoolPtr(filter->ops.name, NULL, 0);
|
||||
if (igIsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
|
||||
mouseIdx = i;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -119,10 +119,15 @@ bool egl_shaderCompile(EGL_Shader * this, const char * vertex_code,
|
||||
if (logLength > 0)
|
||||
{
|
||||
char *log = malloc(logLength + 1);
|
||||
glGetShaderInfoLog(vertexShader, logLength, NULL, log);
|
||||
log[logLength] = 0;
|
||||
DEBUG_ERROR("%s", log);
|
||||
free(log);
|
||||
if (!log)
|
||||
DEBUG_ERROR("out of memory");
|
||||
else
|
||||
{
|
||||
glGetShaderInfoLog(vertexShader, logLength, NULL, log);
|
||||
log[logLength] = 0;
|
||||
DEBUG_ERROR("%s", log);
|
||||
free(log);
|
||||
}
|
||||
}
|
||||
|
||||
glDeleteShader(vertexShader);
|
||||
@@ -145,10 +150,15 @@ bool egl_shaderCompile(EGL_Shader * this, const char * vertex_code,
|
||||
if (logLength > 0)
|
||||
{
|
||||
char *log = malloc(logLength + 1);
|
||||
glGetShaderInfoLog(fragmentShader, logLength, NULL, log);
|
||||
log[logLength] = 0;
|
||||
DEBUG_ERROR("%s", log);
|
||||
free(log);
|
||||
if (!log)
|
||||
DEBUG_ERROR("out of memory");
|
||||
else
|
||||
{
|
||||
glGetShaderInfoLog(fragmentShader, logLength, NULL, log);
|
||||
log[logLength] = 0;
|
||||
DEBUG_ERROR("%s", log);
|
||||
free(log);
|
||||
}
|
||||
}
|
||||
|
||||
glDeleteShader(fragmentShader);
|
||||
@@ -201,6 +211,12 @@ void egl_shaderSetUniforms(EGL_Shader * this, EGL_Uniform * uniforms, int count)
|
||||
{
|
||||
free(this->uniforms);
|
||||
this->uniforms = malloc(sizeof(*this->uniforms) * count);
|
||||
if (!this->uniforms)
|
||||
{
|
||||
DEBUG_ERROR("out of memory");
|
||||
return;
|
||||
}
|
||||
|
||||
this->uniformCount = count;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -5,11 +5,25 @@ in vec2 uv;
|
||||
out vec4 color;
|
||||
|
||||
uniform sampler2D sampler1;
|
||||
uniform float scale;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 tmp = texture(sampler1, uv);
|
||||
vec4 tmp;
|
||||
if (scale > 1.0)
|
||||
{
|
||||
vec2 ts = vec2(textureSize(sampler1, 0));
|
||||
vec2 px = (uv - (0.5 / ts)) * ts;
|
||||
if (px.x < 0.0 || px.y < 0.0)
|
||||
discard;
|
||||
|
||||
tmp = texelFetch(sampler1, ivec2(px), 0);
|
||||
}
|
||||
else
|
||||
tmp = texture(sampler1, uv);
|
||||
|
||||
if (tmp.rgb == vec3(0.0, 0.0, 0.0))
|
||||
discard;
|
||||
|
||||
color = tmp;
|
||||
}
|
||||
|
||||
@@ -3,16 +3,26 @@ precision mediump float;
|
||||
|
||||
#include "color_blind.h"
|
||||
|
||||
in vec2 uv;
|
||||
out vec4 color;
|
||||
in vec2 uv;
|
||||
out vec4 color;
|
||||
|
||||
uniform sampler2D sampler1;
|
||||
|
||||
uniform int cbMode;
|
||||
uniform float scale;
|
||||
uniform int cbMode;
|
||||
|
||||
void main()
|
||||
{
|
||||
color = texture(sampler1, uv);
|
||||
if (scale > 1.0)
|
||||
{
|
||||
vec2 ts = vec2(textureSize(sampler1, 0));
|
||||
vec2 px = (uv - (0.5 / ts)) * ts;
|
||||
if (px.x < 0.0 || px.y < 0.0)
|
||||
discard;
|
||||
|
||||
color = texelFetch(sampler1, ivec2(px), 0);
|
||||
}
|
||||
else
|
||||
color = texture(sampler1, uv);
|
||||
|
||||
if (cbMode > 0)
|
||||
color = cbTransform(color, cbMode);
|
||||
|
||||
@@ -37,8 +37,9 @@ void main()
|
||||
|
||||
if (nvGain > 0.0)
|
||||
{
|
||||
highp float lumi = 1.0 - (0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b);
|
||||
color *= 1.0 + lumi;
|
||||
highp float lumi = (0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b);
|
||||
if (lumi < 0.5)
|
||||
color *= atanh((1.0 - lumi) * 2.0 - 1.0) + 1.0;
|
||||
color *= nvGain;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -29,7 +29,6 @@
|
||||
#include "common/types.h"
|
||||
|
||||
#include "util.h"
|
||||
#include "ll.h"
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -225,7 +225,7 @@ EGL_TexStatus egl_texBufferStreamGet(EGL_Texture * texture, GLuint * tex)
|
||||
|
||||
if (this->sync)
|
||||
{
|
||||
switch(glClientWaitSync(this->sync, 0, 20000000)) // 20ms
|
||||
switch(glClientWaitSync(this->sync, 0, 40000000)) // 40ms
|
||||
{
|
||||
case GL_ALREADY_SIGNALED:
|
||||
case GL_CONDITION_SATISFIED:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -33,7 +33,6 @@ typedef struct TextureBuffer
|
||||
|
||||
int texCount;
|
||||
GLuint tex[EGL_TEX_BUFFER_MAX];
|
||||
GLuint sampler;
|
||||
EGL_TexBuffer buf[EGL_TEX_BUFFER_MAX];
|
||||
int bufFree;
|
||||
GLsync sync;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -36,7 +36,6 @@
|
||||
#include "common/framebuffer.h"
|
||||
#include "common/locking.h"
|
||||
#include "gl_dynprocs.h"
|
||||
#include "ll.h"
|
||||
#include "util.h"
|
||||
|
||||
#define BUFFER_COUNT 2
|
||||
@@ -300,14 +299,15 @@ void opengl_onResize(LG_Renderer * renderer, const int width, const int height,
|
||||
{
|
||||
glTranslatef(this->destRect.x, this->destRect.y, 0.0f);
|
||||
glScalef(
|
||||
(float)this->destRect.w / (float)this->format.width,
|
||||
(float)this->destRect.h / (float)this->format.height,
|
||||
(float)this->destRect.w / (float)this->format.frameWidth,
|
||||
(float)this->destRect.h / (float)this->format.frameHeight,
|
||||
1.0f
|
||||
);
|
||||
}
|
||||
|
||||
// this is needed to refresh the font atlas texture
|
||||
ImGui_ImplOpenGL2_Shutdown();
|
||||
ImGui_ImplOpenGL2_Init();
|
||||
ImGui_ImplOpenGL2_NewFrame();
|
||||
}
|
||||
|
||||
@@ -327,7 +327,14 @@ bool opengl_onMouseShape(LG_Renderer * renderer, const LG_RendererCursor cursor,
|
||||
{
|
||||
if (this->mouseData)
|
||||
free(this->mouseData);
|
||||
this->mouseData = malloc(size);
|
||||
|
||||
this->mouseData = malloc(size);
|
||||
if (!this->mouseData)
|
||||
{
|
||||
DEBUG_ERROR("out of memory");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->mouseDataSize = size;
|
||||
}
|
||||
|
||||
@@ -338,7 +345,8 @@ bool opengl_onMouseShape(LG_Renderer * renderer, const LG_RendererCursor cursor,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool opengl_onMouseEvent(LG_Renderer * renderer, const bool visible, const int x, const int y)
|
||||
bool opengl_onMouseEvent(LG_Renderer * renderer, const bool visible,
|
||||
int x, int y, const int hx, const int hy)
|
||||
{
|
||||
struct Inst * this = UPCAST(struct Inst, renderer);
|
||||
|
||||
@@ -528,7 +536,8 @@ bool opengl_render(LG_Renderer * renderer, LG_RendererRotate rotate, const bool
|
||||
return true;
|
||||
}
|
||||
|
||||
void drawTorus(float x, float y, float inner, float outer, unsigned int pts)
|
||||
static void drawTorus(float x, float y, float inner, float outer,
|
||||
unsigned int pts)
|
||||
{
|
||||
glBegin(GL_QUAD_STRIP);
|
||||
for (unsigned int i = 0; i <= pts; ++i)
|
||||
@@ -540,7 +549,8 @@ void drawTorus(float x, float y, float inner, float outer, unsigned int pts)
|
||||
glEnd();
|
||||
}
|
||||
|
||||
void drawTorusArc(float x, float y, float inner, float outer, unsigned int pts, float s, float e)
|
||||
static void drawTorusArc(float x, float y, float inner, float outer,
|
||||
unsigned int pts, float s, float e)
|
||||
{
|
||||
glBegin(GL_QUAD_STRIP);
|
||||
for (unsigned int i = 0; i <= pts; ++i)
|
||||
@@ -728,7 +738,7 @@ static enum ConfigStatus configure(struct Inst * this)
|
||||
}
|
||||
|
||||
// calculate the texture size in bytes
|
||||
this->texSize = this->format.height * this->format.pitch;
|
||||
this->texSize = this->format.frameHeight * this->format.pitch;
|
||||
this->texPos = 0;
|
||||
|
||||
g_gl_dynProcs.glGenBuffers(BUFFER_COUNT, this->vboID);
|
||||
@@ -825,8 +835,8 @@ static enum ConfigStatus configure(struct Inst * this)
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
this->intFormat,
|
||||
this->format.width,
|
||||
this->format.height,
|
||||
this->format.frameWidth,
|
||||
this->format.frameHeight,
|
||||
0,
|
||||
this->vboFormat,
|
||||
this->dataFormat,
|
||||
@@ -849,10 +859,11 @@ static enum ConfigStatus configure(struct Inst * this)
|
||||
glBindTexture(GL_TEXTURE_2D, this->frames[i]);
|
||||
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
glBegin(GL_TRIANGLE_STRIP);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex2i(0 , 0 );
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex2i(this->format.width, 0 );
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex2i(0 , this->format.height);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex2i(this->format.width, this->format.height);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex2i(0, 0);
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex2i(this->format.frameWidth, 0);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex2i(0, this->format.frameHeight);
|
||||
glTexCoord2f(1.0f, 1.0f);
|
||||
glVertex2i(this->format.frameWidth, this->format.frameHeight);
|
||||
glEnd();
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glEndList();
|
||||
@@ -1109,14 +1120,14 @@ static bool drawFrame(struct Inst * this)
|
||||
|
||||
const int bpp = this->format.bpp / 8;
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT , bpp);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, this->format.width);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, this->format.frameWidth);
|
||||
|
||||
this->texPos = 0;
|
||||
|
||||
framebuffer_read_fn(
|
||||
this->frame,
|
||||
this->format.height,
|
||||
this->format.width,
|
||||
this->format.frameHeight,
|
||||
this->format.frameWidth,
|
||||
bpp,
|
||||
this->format.pitch,
|
||||
opengl_bufferFn,
|
||||
@@ -1131,8 +1142,8 @@ static bool drawFrame(struct Inst * this)
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
this->format.width ,
|
||||
this->format.height,
|
||||
this->format.frameWidth ,
|
||||
this->format.frameHeight,
|
||||
this->vboFormat,
|
||||
this->dataFormat,
|
||||
(void*)0
|
||||
@@ -1140,7 +1151,8 @@ static bool drawFrame(struct Inst * this)
|
||||
if (check_gl_error("glTexSubImage2D"))
|
||||
{
|
||||
DEBUG_ERROR("texWIndex: %u, width: %u, height: %u, vboFormat: %x, texSize: %lu",
|
||||
this->texWIndex, this->format.width, this->format.height, this->vboFormat, this->texSize
|
||||
this->texWIndex, this->format.frameWidth, this->format.frameHeight,
|
||||
this->vboFormat, this->texSize
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1148,8 +1160,8 @@ static bool drawFrame(struct Inst * this)
|
||||
g_gl_dynProcs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
const bool mipmap = this->opt.mipmap && (
|
||||
(this->format.width > this->destRect.w) ||
|
||||
(this->format.height > this->destRect.h));
|
||||
(this->format.frameWidth > this->destRect.w) ||
|
||||
(this->format.frameHeight > this->destRect.h));
|
||||
|
||||
if (mipmap)
|
||||
{
|
||||
|
||||
298
client/src/app.c
298
client/src/app.c
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -25,7 +25,6 @@
|
||||
#include "util.h"
|
||||
#include "clipboard.h"
|
||||
|
||||
#include "ll.h"
|
||||
#include "kb.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
@@ -39,8 +38,6 @@
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
#define ALERT_TIMEOUT 2000000
|
||||
|
||||
bool app_isRunning(void)
|
||||
{
|
||||
return
|
||||
@@ -65,7 +62,23 @@ bool app_isFormatValid(void)
|
||||
|
||||
bool app_isOverlayMode(void)
|
||||
{
|
||||
return g_state.overlayInput;
|
||||
if (g_state.overlayInput)
|
||||
return true;
|
||||
|
||||
bool result = false;
|
||||
struct Overlay * overlay;
|
||||
ll_lock(g_state.overlays);
|
||||
ll_forEachNL(g_state.overlays, item, overlay)
|
||||
{
|
||||
if (overlay->ops->needs_overlay && overlay->ops->needs_overlay(overlay))
|
||||
{
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ll_unlock(g_state.overlays);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void app_updateCursorPos(double x, double y)
|
||||
@@ -74,7 +87,7 @@ void app_updateCursorPos(double x, double y)
|
||||
g_cursor.pos.y = y;
|
||||
g_cursor.valid = true;
|
||||
|
||||
if (g_state.overlayInput)
|
||||
if (app_isOverlayMode())
|
||||
g_state.io->MousePos = (ImVec2) { x, y };
|
||||
}
|
||||
|
||||
@@ -83,7 +96,7 @@ void app_handleFocusEvent(bool focused)
|
||||
g_state.focused = focused;
|
||||
|
||||
// release any imgui buttons/keys if we lost focus
|
||||
if (!focused && g_state.overlayInput)
|
||||
if (!focused && app_isOverlayMode())
|
||||
core_resetOverlayInputState();
|
||||
|
||||
if (!core_inputEnabled())
|
||||
@@ -133,7 +146,7 @@ void app_handleEnterEvent(bool entered)
|
||||
|
||||
// stop the user being able to drag windows off the screen and work around
|
||||
// the mouse button release being missed due to not being in capture mode.
|
||||
if (g_state.overlayInput)
|
||||
if (app_isOverlayMode())
|
||||
{
|
||||
g_state.io->MouseDown[ImGuiMouseButton_Left ] = false;
|
||||
g_state.io->MouseDown[ImGuiMouseButton_Right ] = false;
|
||||
@@ -154,7 +167,7 @@ void app_clipboardRelease(void)
|
||||
if (!g_params.clipboardToVM)
|
||||
return;
|
||||
|
||||
spice_clipboard_release();
|
||||
purespice_clipboardRelease();
|
||||
}
|
||||
|
||||
void app_clipboardNotifyTypes(const LG_ClipboardData types[], int count)
|
||||
@@ -164,15 +177,15 @@ void app_clipboardNotifyTypes(const LG_ClipboardData types[], int count)
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
spice_clipboard_release();
|
||||
purespice_clipboardRelease();
|
||||
return;
|
||||
}
|
||||
|
||||
SpiceDataType conv[count];
|
||||
PSDataType conv[count];
|
||||
for(int i = 0; i < count; ++i)
|
||||
conv[i] = cb_lgTypeToSpiceType(types[i]);
|
||||
|
||||
spice_clipboard_grab(conv, count);
|
||||
purespice_clipboardGrab(conv, count);
|
||||
}
|
||||
|
||||
void app_clipboardNotifySize(const LG_ClipboardData type, size_t size)
|
||||
@@ -182,7 +195,7 @@ void app_clipboardNotifySize(const LG_ClipboardData type, size_t size)
|
||||
|
||||
if (type == LG_CLIPBOARD_DATA_NONE)
|
||||
{
|
||||
spice_clipboard_release();
|
||||
purespice_clipboardRelease();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -190,7 +203,7 @@ void app_clipboardNotifySize(const LG_ClipboardData type, size_t size)
|
||||
g_state.cbChunked = size > 0;
|
||||
g_state.cbXfer = size;
|
||||
|
||||
spice_clipboard_data_start(g_state.cbType, size);
|
||||
purespice_clipboardDataStart(g_state.cbType, size);
|
||||
}
|
||||
|
||||
void app_clipboardData(const LG_ClipboardData type, uint8_t * data, size_t size)
|
||||
@@ -205,9 +218,9 @@ void app_clipboardData(const LG_ClipboardData type, uint8_t * data, size_t size)
|
||||
}
|
||||
|
||||
if (!g_state.cbChunked)
|
||||
spice_clipboard_data_start(g_state.cbType, size);
|
||||
purespice_clipboardDataStart(g_state.cbType, size);
|
||||
|
||||
spice_clipboard_data(g_state.cbType, data, (uint32_t)size);
|
||||
purespice_clipboardData(g_state.cbType, data, (uint32_t)size);
|
||||
g_state.cbXfer -= size;
|
||||
}
|
||||
|
||||
@@ -217,13 +230,18 @@ void app_clipboardRequest(const LG_ClipboardReplyFn replyFn, void * opaque)
|
||||
return;
|
||||
|
||||
struct CBRequest * cbr = malloc(sizeof(*cbr));
|
||||
if (!cbr)
|
||||
{
|
||||
DEBUG_ERROR("out of memory");
|
||||
return;
|
||||
}
|
||||
|
||||
cbr->type = g_state.cbType;
|
||||
cbr->replyFn = replyFn;
|
||||
cbr->opaque = opaque;
|
||||
ll_push(g_state.cbRequestList, cbr);
|
||||
|
||||
spice_clipboard_request(g_state.cbType);
|
||||
purespice_clipboardRequest(g_state.cbType);
|
||||
}
|
||||
|
||||
static int mapSpiceToImGuiButton(uint32_t button)
|
||||
@@ -245,7 +263,7 @@ void app_handleButtonPress(int button)
|
||||
{
|
||||
g_cursor.buttons |= (1U << button);
|
||||
|
||||
if (g_state.overlayInput)
|
||||
if (app_isOverlayMode())
|
||||
{
|
||||
int igButton = mapSpiceToImGuiButton(button);
|
||||
if (igButton != -1)
|
||||
@@ -256,7 +274,7 @@ void app_handleButtonPress(int button)
|
||||
if (!core_inputEnabled() || !g_cursor.inView)
|
||||
return;
|
||||
|
||||
if (!spice_mouse_press(button))
|
||||
if (!purespice_mousePress(button))
|
||||
DEBUG_ERROR("app_handleButtonPress: failed to send message");
|
||||
}
|
||||
|
||||
@@ -264,7 +282,7 @@ void app_handleButtonRelease(int button)
|
||||
{
|
||||
g_cursor.buttons &= ~(1U << button);
|
||||
|
||||
if (g_state.overlayInput)
|
||||
if (app_isOverlayMode())
|
||||
{
|
||||
int igButton = mapSpiceToImGuiButton(button);
|
||||
if (igButton != -1)
|
||||
@@ -275,19 +293,19 @@ void app_handleButtonRelease(int button)
|
||||
if (!core_inputEnabled())
|
||||
return;
|
||||
|
||||
if (!spice_mouse_release(button))
|
||||
if (!purespice_mouseRelease(button))
|
||||
DEBUG_ERROR("app_handleButtonRelease: failed to send message");
|
||||
}
|
||||
|
||||
void app_handleWheelMotion(double motion)
|
||||
{
|
||||
if (g_state.overlayInput)
|
||||
if (app_isOverlayMode())
|
||||
g_state.io->MouseWheel -= motion;
|
||||
}
|
||||
|
||||
void app_handleKeyPress(int sc)
|
||||
{
|
||||
if (!g_state.overlayInput || !g_state.io->WantCaptureKeyboard)
|
||||
if (!app_isOverlayMode() || !g_state.io->WantCaptureKeyboard)
|
||||
{
|
||||
if (sc == g_params.escapeKey && !g_state.escapeActive)
|
||||
{
|
||||
@@ -300,11 +318,14 @@ void app_handleKeyPress(int sc)
|
||||
if (g_state.escapeActive)
|
||||
{
|
||||
g_state.escapeAction = sc;
|
||||
KeybindHandle handle = g_state.bindings[sc];
|
||||
if (handle)
|
||||
handle->callback(sc, handle->opaque);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (g_state.overlayInput)
|
||||
if (app_isOverlayMode())
|
||||
{
|
||||
if (sc == KEY_ESC)
|
||||
app_setOverlay(false);
|
||||
@@ -325,7 +346,7 @@ void app_handleKeyPress(int sc)
|
||||
if (!ps2)
|
||||
return;
|
||||
|
||||
if (spice_key_down(ps2))
|
||||
if (purespice_keyDown(ps2))
|
||||
g_state.keyDown[sc] = true;
|
||||
else
|
||||
{
|
||||
@@ -341,24 +362,16 @@ void app_handleKeyRelease(int sc)
|
||||
{
|
||||
if (g_state.escapeAction == -1)
|
||||
{
|
||||
if (!g_state.escapeHelp && g_params.useSpiceInput && !g_state.overlayInput)
|
||||
if (!g_state.escapeHelp && g_params.useSpiceInput &&
|
||||
!app_isOverlayMode())
|
||||
core_setGrab(!g_cursor.grab);
|
||||
}
|
||||
else
|
||||
{
|
||||
KeybindHandle handle = g_state.bindings[sc];
|
||||
if (handle)
|
||||
{
|
||||
handle->callback(sc, handle->opaque);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (sc == g_params.escapeKey)
|
||||
g_state.escapeActive = false;
|
||||
}
|
||||
|
||||
if (g_state.overlayInput)
|
||||
if (app_isOverlayMode())
|
||||
{
|
||||
g_state.io->KeysDown[sc] = false;
|
||||
return;
|
||||
@@ -378,7 +391,7 @@ void app_handleKeyRelease(int sc)
|
||||
if (!ps2)
|
||||
return;
|
||||
|
||||
if (spice_key_up(ps2))
|
||||
if (purespice_keyUp(ps2))
|
||||
g_state.keyDown[sc] = false;
|
||||
else
|
||||
{
|
||||
@@ -410,14 +423,14 @@ void app_handleKeyboardLEDs(bool numLock, bool capsLock, bool scrollLock)
|
||||
(numLock ? 2 /* SPICE_NUM_LOCK_MODIFIER */ : 0) |
|
||||
(capsLock ? 4 /* SPICE_CAPS_LOCK_MODIFIER */ : 0);
|
||||
|
||||
if (!spice_key_modifiers(modifiers))
|
||||
if (!purespice_keyModifiers(modifiers))
|
||||
DEBUG_ERROR("app_handleKeyboardLEDs: failed to send message");
|
||||
}
|
||||
|
||||
void app_handleMouseRelative(double normx, double normy,
|
||||
double rawx, double rawy)
|
||||
{
|
||||
if (g_state.overlayInput)
|
||||
if (app_isOverlayMode())
|
||||
return;
|
||||
|
||||
if (g_cursor.grab)
|
||||
@@ -439,7 +452,8 @@ void app_handleMouseRelative(double normx, double normy,
|
||||
void app_handleMouseBasic()
|
||||
{
|
||||
/* do not pass mouse events to the guest if we do not have focus */
|
||||
if (!g_cursor.guest.valid || !g_state.haveSrcSize || !g_state.focused || g_state.overlayInput)
|
||||
if (!g_cursor.guest.valid || !g_state.haveSrcSize || !g_state.focused ||
|
||||
app_isOverlayMode())
|
||||
return;
|
||||
|
||||
if (!core_inputEnabled())
|
||||
@@ -468,7 +482,7 @@ void app_handleMouseBasic()
|
||||
g_cursor.projected.x += x;
|
||||
g_cursor.projected.y += y;
|
||||
|
||||
if (!spice_mouse_motion(x, y))
|
||||
if (!purespice_mouseMotion(x, y))
|
||||
DEBUG_ERROR("failed to send mouse motion message");
|
||||
}
|
||||
|
||||
@@ -519,6 +533,10 @@ void app_invalidateWindow(bool full)
|
||||
{
|
||||
if (full)
|
||||
atomic_store(&g_state.invalidateWindow, true);
|
||||
|
||||
if (g_state.jitRender && g_state.ds->stopWaitFrame)
|
||||
g_state.ds->stopWaitFrame();
|
||||
|
||||
lgSignalEvent(g_state.frameEvent);
|
||||
}
|
||||
|
||||
@@ -548,15 +566,6 @@ void app_handleRenderEvent(const uint64_t timeUs)
|
||||
}
|
||||
}
|
||||
|
||||
if (g_state.alertShow)
|
||||
if (g_state.alertTimeout < timeUs)
|
||||
{
|
||||
g_state.alertShow = false;
|
||||
free(g_state.alertMessage);
|
||||
g_state.alertMessage = NULL;
|
||||
invalidate = true;
|
||||
}
|
||||
|
||||
if (invalidate)
|
||||
app_invalidateWindow(false);
|
||||
}
|
||||
@@ -625,19 +634,48 @@ void app_alert(LG_MsgAlert type, const char * fmt, ...)
|
||||
if (!g_state.lgr || !g_params.showAlerts)
|
||||
return;
|
||||
|
||||
char * buffer;
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
valloc_sprintf(&buffer, fmt, args);
|
||||
overlayAlert_show(type, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
MsgBoxHandle app_msgBox(const char * caption, const char * fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
MsgBoxHandle handle = overlayMsg_show(caption, NULL, NULL, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
free(g_state.alertMessage);
|
||||
g_state.alertMessage = buffer;
|
||||
g_state.alertTimeout = microtime() + ALERT_TIMEOUT;
|
||||
g_state.alertType = type;
|
||||
g_state.alertShow = true;
|
||||
app_invalidateWindow(false);
|
||||
core_updateOverlayState();
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
MsgBoxHandle app_confirmMsgBox(const char * caption,
|
||||
MsgBoxConfirmCallback callback, void * opaque, const char * fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
MsgBoxHandle handle = overlayMsg_show(caption, callback, opaque, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
core_updateOverlayState();
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
void app_msgBoxClose(MsgBoxHandle handle)
|
||||
{
|
||||
if (!handle)
|
||||
return;
|
||||
|
||||
overlayMsg_close(handle);
|
||||
}
|
||||
|
||||
void app_showRecord(bool show)
|
||||
{
|
||||
overlayRecord_show(show);
|
||||
}
|
||||
|
||||
KeybindHandle app_registerKeybind(int sc, KeybindFn callback, void * opaque, const char * description)
|
||||
@@ -650,6 +688,12 @@ KeybindHandle app_registerKeybind(int sc, KeybindFn callback, void * opaque, con
|
||||
}
|
||||
|
||||
KeybindHandle handle = malloc(sizeof(*handle));
|
||||
if (!handle)
|
||||
{
|
||||
DEBUG_ERROR("out of memory");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
handle->sc = sc;
|
||||
handle->callback = callback;
|
||||
handle->opaque = opaque;
|
||||
@@ -679,9 +723,10 @@ void app_releaseAllKeybinds(void)
|
||||
}
|
||||
}
|
||||
|
||||
GraphHandle app_registerGraph(const char * name, RingBuffer buffer, float min, float max)
|
||||
GraphHandle app_registerGraph(const char * name, RingBuffer buffer,
|
||||
float min, float max, GraphFormatFn formatFn)
|
||||
{
|
||||
return overlayGraph_register(name, buffer, min, max);
|
||||
return overlayGraph_register(name, buffer, min, max, formatFn);
|
||||
}
|
||||
|
||||
void app_unregisterGraph(GraphHandle handle)
|
||||
@@ -689,20 +734,22 @@ void app_unregisterGraph(GraphHandle handle)
|
||||
overlayGraph_unregister(handle);
|
||||
}
|
||||
|
||||
struct Overlay
|
||||
void app_invalidateGraph(GraphHandle handle)
|
||||
{
|
||||
const struct LG_OverlayOps * ops;
|
||||
const void * params;
|
||||
void * udata;
|
||||
int lastRectCount;
|
||||
struct Rect lastRects[MAX_OVERLAY_RECTS];
|
||||
};
|
||||
overlayGraph_invalidate(handle);
|
||||
}
|
||||
|
||||
void app_registerOverlay(const struct LG_OverlayOps * ops, const void * params)
|
||||
{
|
||||
ASSERT_LG_OVERLAY_VALID(ops);
|
||||
|
||||
struct Overlay * overlay = malloc(sizeof(*overlay));
|
||||
if (!overlay)
|
||||
{
|
||||
DEBUG_ERROR("out of ram");
|
||||
return;
|
||||
}
|
||||
|
||||
overlay->ops = ops;
|
||||
overlay->params = params;
|
||||
overlay->udata = NULL;
|
||||
@@ -716,15 +763,17 @@ void app_registerOverlay(const struct LG_OverlayOps * ops, const void * params)
|
||||
void app_initOverlays(void)
|
||||
{
|
||||
struct Overlay * overlay;
|
||||
for (ll_reset(g_state.overlays);
|
||||
ll_walk(g_state.overlays, (void **)&overlay); )
|
||||
ll_lock(g_state.overlays);
|
||||
ll_forEachNL(g_state.overlays, item, overlay)
|
||||
{
|
||||
DEBUG_ASSERT(overlay->ops);
|
||||
if (!overlay->ops->init(&overlay->udata, overlay->params))
|
||||
{
|
||||
DEBUG_ERROR("Overlay `%s` failed to initialize", overlay->ops->name);
|
||||
overlay->ops = NULL;
|
||||
}
|
||||
}
|
||||
ll_unlock(g_state.overlays);
|
||||
}
|
||||
|
||||
static inline void mergeRect(struct Rect * dest, const struct Rect * a, const struct Rect * b)
|
||||
@@ -769,22 +818,26 @@ static inline LG_DSPointer mapImGuiCursor(ImGuiMouseCursor cursor)
|
||||
|
||||
bool app_overlayNeedsRender(void)
|
||||
{
|
||||
struct Overlay * overlay;
|
||||
|
||||
if (g_state.overlayInput)
|
||||
if (app_isOverlayMode())
|
||||
return true;
|
||||
|
||||
for (ll_reset(g_state.overlays);
|
||||
ll_walk(g_state.overlays, (void **)&overlay); )
|
||||
bool result = false;
|
||||
struct Overlay * overlay;
|
||||
ll_lock(g_state.overlays);
|
||||
ll_forEachNL(g_state.overlays, item, overlay)
|
||||
{
|
||||
if (!overlay->ops->needs_render)
|
||||
continue;
|
||||
|
||||
if (overlay->ops->needs_render(overlay->udata, g_state.overlayInput))
|
||||
return true;
|
||||
if (overlay->ops->needs_render(overlay->udata, false))
|
||||
{
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ll_unlock(g_state.overlays);
|
||||
|
||||
return false;
|
||||
return result;
|
||||
}
|
||||
|
||||
int app_renderOverlay(struct Rect * rects, int maxRects)
|
||||
@@ -803,24 +856,34 @@ int app_renderOverlay(struct Rect * rects, int maxRects)
|
||||
g_state.io->DeltaTime = (now - g_state.lastImGuiFrame) * 1e-9f;
|
||||
g_state.lastImGuiFrame = now;
|
||||
|
||||
render_again:
|
||||
|
||||
igNewFrame();
|
||||
|
||||
if (g_state.overlayInput)
|
||||
const bool overlayMode = app_isOverlayMode();
|
||||
if (overlayMode)
|
||||
{
|
||||
totalDamage = true;
|
||||
ImDrawList_AddRectFilled(igGetBackgroundDrawListNil(), (ImVec2) { 0.0f , 0.0f },
|
||||
g_state.io->DisplaySize, igGetColorU32Col(ImGuiCol_ModalWindowDimBg, 1.0f), 0, 0);
|
||||
ImDrawList_AddRectFilled(igGetBackgroundDrawList_Nil(), (ImVec2) { 0.0f , 0.0f },
|
||||
g_state.io->DisplaySize,
|
||||
igGetColorU32_Col(ImGuiCol_ModalWindowDimBg, 1.0f),
|
||||
0, 0);
|
||||
|
||||
// bool test;
|
||||
// igShowDemoWindow(&test);
|
||||
}
|
||||
|
||||
const bool msgModal = overlayMsg_modal();
|
||||
|
||||
// render the overlays
|
||||
for (ll_reset(g_state.overlays);
|
||||
ll_walk(g_state.overlays, (void **)&overlay); )
|
||||
ll_lock(g_state.overlays);
|
||||
ll_forEachNL(g_state.overlays, item, overlay)
|
||||
{
|
||||
if (msgModal && overlay->ops != &LGOverlayMsg)
|
||||
continue;
|
||||
|
||||
const int written =
|
||||
overlay->ops->render(overlay->udata, g_state.overlayInput,
|
||||
overlay->ops->render(overlay->udata, overlayMode,
|
||||
buffer, MAX_OVERLAY_RECTS);
|
||||
|
||||
for (int i = 0; i < written; ++i)
|
||||
@@ -856,8 +919,9 @@ int app_renderOverlay(struct Rect * rects, int maxRects)
|
||||
memcpy(overlay->lastRects, buffer, sizeof(struct Rect) * written);
|
||||
overlay->lastRectCount = written;
|
||||
}
|
||||
ll_unlock(g_state.overlays);
|
||||
|
||||
if (g_state.overlayInput)
|
||||
if (overlayMode)
|
||||
{
|
||||
ImGuiMouseCursor cursor = igGetMouseCursor();
|
||||
if (cursor != g_state.cursorLast)
|
||||
@@ -869,6 +933,16 @@ int app_renderOverlay(struct Rect * rects, int maxRects)
|
||||
|
||||
igRender();
|
||||
|
||||
/* imgui requires two passes to calculate the bounding box of auto sized
|
||||
* windows, this is by design
|
||||
* ref: https://github.com/ocornut/imgui/issues/2158#issuecomment-434223618
|
||||
*/
|
||||
if (g_state.renderImGuiTwice)
|
||||
{
|
||||
g_state.renderImGuiTwice = false;
|
||||
goto render_again;
|
||||
}
|
||||
|
||||
return totalDamage ? -1 : totalRects;
|
||||
}
|
||||
|
||||
@@ -884,31 +958,11 @@ void app_freeOverlays(void)
|
||||
|
||||
void app_setOverlay(bool enable)
|
||||
{
|
||||
static bool wasGrabbed = false;
|
||||
|
||||
if (g_state.overlayInput == enable)
|
||||
return;
|
||||
|
||||
g_state.overlayInput = enable;
|
||||
g_state.cursorLast = -2;
|
||||
|
||||
if (g_state.overlayInput)
|
||||
{
|
||||
wasGrabbed = g_cursor.grab;
|
||||
|
||||
g_state.io->ConfigFlags &= ~ImGuiConfigFlags_NoMouse;
|
||||
g_state.io->MousePos = (ImVec2) { g_cursor.pos.x, g_cursor.pos.y };
|
||||
|
||||
core_setGrabQuiet(false);
|
||||
core_setCursorInView(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_state.io->ConfigFlags |= ImGuiConfigFlags_NoMouse;
|
||||
core_resetOverlayInputState();
|
||||
core_setGrabQuiet(wasGrabbed);
|
||||
app_invalidateWindow(false);
|
||||
}
|
||||
core_updateOverlayState();
|
||||
}
|
||||
|
||||
void app_overlayConfigRegister(const char * title,
|
||||
@@ -922,3 +976,35 @@ void app_overlayConfigRegisterTab(const char * title,
|
||||
{
|
||||
overlayConfig_registerTab(title, callback, udata);
|
||||
}
|
||||
|
||||
void app_invalidateOverlay(bool renderTwice)
|
||||
{
|
||||
if (renderTwice)
|
||||
g_state.renderImGuiTwice = true;
|
||||
app_invalidateWindow(false);
|
||||
}
|
||||
|
||||
bool app_guestIsLinux(void)
|
||||
{
|
||||
return g_state.guestOS == KVMFR_OS_LINUX;
|
||||
}
|
||||
|
||||
bool app_guestIsWindows(void)
|
||||
{
|
||||
return g_state.guestOS == KVMFR_OS_WINDOWS;
|
||||
}
|
||||
|
||||
bool app_guestIsOSX(void)
|
||||
{
|
||||
return g_state.guestOS == KVMFR_OS_OSX;
|
||||
}
|
||||
|
||||
bool app_guestIsBSD(void)
|
||||
{
|
||||
return g_state.guestOS == KVMFR_OS_BSD;
|
||||
}
|
||||
|
||||
bool app_guestIsOther(void)
|
||||
{
|
||||
return g_state.guestOS == KVMFR_OS_OTHER;
|
||||
}
|
||||
|
||||
873
client/src/audio.c
Normal file
873
client/src/audio.c
Normal file
@@ -0,0 +1,873 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2022 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
|
||||
*/
|
||||
|
||||
#if ENABLE_AUDIO
|
||||
|
||||
#include "audio.h"
|
||||
#include "main.h"
|
||||
#include "common/array.h"
|
||||
#include "common/util.h"
|
||||
#include "common/ringbuffer.h"
|
||||
|
||||
#include "dynamic/audiodev.h"
|
||||
|
||||
#include <float.h>
|
||||
#include <math.h>
|
||||
#include <samplerate.h>
|
||||
#include <stdalign.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef enum
|
||||
{
|
||||
STREAM_STATE_STOP,
|
||||
STREAM_STATE_SETUP_SPICE,
|
||||
STREAM_STATE_SETUP_DEVICE,
|
||||
STREAM_STATE_RUN,
|
||||
STREAM_STATE_KEEP_ALIVE
|
||||
}
|
||||
StreamState;
|
||||
|
||||
#define STREAM_ACTIVE(state) \
|
||||
(state == STREAM_STATE_RUN || state == STREAM_STATE_KEEP_ALIVE)
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int periodFrames;
|
||||
double periodSec;
|
||||
int64_t nextTime;
|
||||
int64_t nextPosition;
|
||||
double b;
|
||||
double c;
|
||||
}
|
||||
PlaybackDeviceData;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
float * framesIn;
|
||||
float * framesOut;
|
||||
int framesOutSize;
|
||||
|
||||
int periodFrames;
|
||||
double periodSec;
|
||||
int64_t nextTime;
|
||||
int64_t nextPosition;
|
||||
double b;
|
||||
double c;
|
||||
|
||||
int devPeriodFrames;
|
||||
int64_t devLastTime;
|
||||
int64_t devNextTime;
|
||||
int64_t devLastPosition;
|
||||
int64_t devNextPosition;
|
||||
|
||||
double offsetError;
|
||||
double offsetErrorIntegral;
|
||||
|
||||
double ratioIntegral;
|
||||
|
||||
SRC_STATE * src;
|
||||
}
|
||||
PlaybackSpiceData;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
struct LG_AudioDevOps * audioDev;
|
||||
|
||||
struct
|
||||
{
|
||||
StreamState state;
|
||||
int volumeChannels;
|
||||
uint16_t volume[8];
|
||||
bool mute;
|
||||
int channels;
|
||||
int sampleRate;
|
||||
int stride;
|
||||
int deviceMaxPeriodFrames;
|
||||
int deviceStartFrames;
|
||||
int targetStartFrames;
|
||||
RingBuffer buffer;
|
||||
RingBuffer deviceTiming;
|
||||
|
||||
RingBuffer timings;
|
||||
GraphHandle graph;
|
||||
|
||||
/* These two structs contain data specifically for use in the device and
|
||||
* Spice data threads respectively. Keep them on separate cache lines to
|
||||
* avoid false sharing. */
|
||||
alignas(64) PlaybackDeviceData deviceData;
|
||||
alignas(64) PlaybackSpiceData spiceData;
|
||||
}
|
||||
playback;
|
||||
|
||||
struct
|
||||
{
|
||||
bool started;
|
||||
int volumeChannels;
|
||||
uint16_t volume[8];
|
||||
bool mute;
|
||||
int stride;
|
||||
uint32_t time;
|
||||
MsgBoxHandle confirmHandle;
|
||||
int confirmChannels;
|
||||
int confirmSampleRate;
|
||||
PSAudioFormat confirmFormat;
|
||||
}
|
||||
record;
|
||||
}
|
||||
AudioState;
|
||||
|
||||
static AudioState audio = { 0 };
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int periodFrames;
|
||||
int64_t nextTime;
|
||||
int64_t nextPosition;
|
||||
}
|
||||
PlaybackDeviceTick;
|
||||
|
||||
static void playbackStop(void);
|
||||
|
||||
void audio_init(void)
|
||||
{
|
||||
// search for the best audiodev to use
|
||||
for(int i = 0; i < LG_AUDIODEV_COUNT; ++i)
|
||||
if (LG_AudioDevs[i]->init())
|
||||
{
|
||||
audio.audioDev = LG_AudioDevs[i];
|
||||
DEBUG_INFO("Using AudioDev: %s", audio.audioDev->name);
|
||||
return;
|
||||
}
|
||||
|
||||
DEBUG_WARN("Failed to initialize an audio backend");
|
||||
}
|
||||
|
||||
void audio_free(void)
|
||||
{
|
||||
if (!audio.audioDev)
|
||||
return;
|
||||
|
||||
// immediate stop of the stream, do not wait for drain
|
||||
playbackStop();
|
||||
audio_recordStop();
|
||||
|
||||
audio.audioDev->free();
|
||||
audio.audioDev = NULL;
|
||||
}
|
||||
|
||||
bool audio_supportsPlayback(void)
|
||||
{
|
||||
return audio.audioDev && audio.audioDev->playback.start;
|
||||
}
|
||||
|
||||
static const char * audioGraphFormatFn(const char * name,
|
||||
float min, float max, float avg, float freq, float last)
|
||||
{
|
||||
static char title[64];
|
||||
snprintf(title, sizeof(title),
|
||||
"%s: min:%4.2f max:%4.2f avg:%4.2f now:%4.2f",
|
||||
name, min, max, avg, last);
|
||||
return title;
|
||||
}
|
||||
|
||||
static void playbackStop(void)
|
||||
{
|
||||
if (audio.playback.state == STREAM_STATE_STOP)
|
||||
return;
|
||||
|
||||
audio.playback.state = STREAM_STATE_STOP;
|
||||
audio.audioDev->playback.stop();
|
||||
ringbuffer_free(&audio.playback.buffer);
|
||||
ringbuffer_free(&audio.playback.deviceTiming);
|
||||
audio.playback.spiceData.src = src_delete(audio.playback.spiceData.src);
|
||||
|
||||
if (audio.playback.spiceData.framesIn)
|
||||
{
|
||||
free(audio.playback.spiceData.framesIn);
|
||||
free(audio.playback.spiceData.framesOut);
|
||||
audio.playback.spiceData.framesIn = NULL;
|
||||
audio.playback.spiceData.framesOut = NULL;
|
||||
}
|
||||
|
||||
if (audio.playback.timings)
|
||||
{
|
||||
app_unregisterGraph(audio.playback.graph);
|
||||
ringbuffer_free(&audio.playback.timings);
|
||||
}
|
||||
}
|
||||
|
||||
static int playbackPullFrames(uint8_t * dst, int frames)
|
||||
{
|
||||
DEBUG_ASSERT(frames >= 0);
|
||||
if (frames == 0)
|
||||
return frames;
|
||||
|
||||
PlaybackDeviceData * data = &audio.playback.deviceData;
|
||||
int64_t now = nanotime();
|
||||
|
||||
if (audio.playback.buffer)
|
||||
{
|
||||
if (audio.playback.state == STREAM_STATE_SETUP_DEVICE)
|
||||
{
|
||||
/* If necessary, slew backwards to play silence until we reach the target
|
||||
* startup latency. This avoids underrunning the buffer if the audio
|
||||
* device starts earlier than required. */
|
||||
int offset = ringbuffer_getCount(audio.playback.buffer) -
|
||||
audio.playback.targetStartFrames;
|
||||
if (offset < 0)
|
||||
{
|
||||
data->nextPosition += offset;
|
||||
ringbuffer_consume(audio.playback.buffer, NULL, offset);
|
||||
}
|
||||
|
||||
audio.playback.state = STREAM_STATE_RUN;
|
||||
}
|
||||
|
||||
// Measure the device clock and post to the Spice thread
|
||||
if (frames != data->periodFrames)
|
||||
{
|
||||
double newPeriodSec = (double) frames / audio.playback.sampleRate;
|
||||
|
||||
bool init = data->periodFrames == 0;
|
||||
if (init)
|
||||
data->nextTime = now + llrint(newPeriodSec * 1.0e9);
|
||||
else
|
||||
/* Due to the double-buffered nature of audio playback, we are filling
|
||||
* in the next buffer while the device is playing the previous buffer.
|
||||
* This results in slightly unintuitive behaviour when the period size
|
||||
* changes. The device will request enough samples for the new period
|
||||
* size, but won't call us again until the previous buffer at the old
|
||||
* size has finished playing. So, to avoid a blip in the timing
|
||||
* calculations, we must set the estimated next wakeup time based upon
|
||||
* the previous period size, not the new one. */
|
||||
data->nextTime += llrint(data->periodSec * 1.0e9);
|
||||
|
||||
data->periodFrames = frames;
|
||||
data->periodSec = newPeriodSec;
|
||||
data->nextPosition += frames;
|
||||
|
||||
double bandwidth = 0.05;
|
||||
double omega = 2.0 * M_PI * bandwidth * data->periodSec;
|
||||
data->b = M_SQRT2 * omega;
|
||||
data->c = omega * omega;
|
||||
}
|
||||
else
|
||||
{
|
||||
double error = (now - data->nextTime) * 1.0e-9;
|
||||
if (fabs(error) >= 0.2)
|
||||
{
|
||||
// Clock error is too high; slew the read pointer and reset the timing
|
||||
// parameters to avoid getting too far out of sync
|
||||
int slewFrames = round(error * audio.playback.sampleRate);
|
||||
ringbuffer_consume(audio.playback.buffer, NULL, slewFrames);
|
||||
|
||||
data->periodSec = (double) frames / audio.playback.sampleRate;
|
||||
data->nextTime = now + llrint(data->periodSec * 1.0e9);
|
||||
data->nextPosition += slewFrames + frames;
|
||||
}
|
||||
else
|
||||
{
|
||||
data->nextTime +=
|
||||
llrint((data->b * error + data->periodSec) * 1.0e9);
|
||||
data->periodSec += data->c * error;
|
||||
data->nextPosition += frames;
|
||||
}
|
||||
}
|
||||
|
||||
PlaybackDeviceTick tick =
|
||||
{
|
||||
.periodFrames = data->periodFrames,
|
||||
.nextTime = data->nextTime,
|
||||
.nextPosition = data->nextPosition
|
||||
};
|
||||
ringbuffer_push(audio.playback.deviceTiming, &tick);
|
||||
|
||||
ringbuffer_consume(audio.playback.buffer, dst, frames);
|
||||
}
|
||||
else
|
||||
frames = 0;
|
||||
|
||||
// Close the stream if nothing has played for a while
|
||||
if (audio.playback.state == STREAM_STATE_KEEP_ALIVE)
|
||||
{
|
||||
int stopTimeSec = 30;
|
||||
int stopTimeFrames = stopTimeSec * audio.playback.sampleRate;
|
||||
if (ringbuffer_getCount(audio.playback.buffer) <= -stopTimeFrames)
|
||||
playbackStop();
|
||||
}
|
||||
|
||||
return frames;
|
||||
}
|
||||
|
||||
void audio_playbackStart(int channels, int sampleRate, PSAudioFormat format,
|
||||
uint32_t time)
|
||||
{
|
||||
if (!audio.audioDev)
|
||||
return;
|
||||
|
||||
static int lastChannels = 0;
|
||||
static int lastSampleRate = 0;
|
||||
|
||||
if (audio.playback.state == STREAM_STATE_KEEP_ALIVE &&
|
||||
channels == lastChannels && sampleRate == lastSampleRate)
|
||||
return;
|
||||
if (audio.playback.state != STREAM_STATE_STOP)
|
||||
playbackStop();
|
||||
|
||||
int srcError;
|
||||
audio.playback.spiceData.src = src_new(SRC_SINC_FASTEST, channels, &srcError);
|
||||
if (!audio.playback.spiceData.src)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create resampler: %s", src_strerror(srcError));
|
||||
return;
|
||||
}
|
||||
|
||||
const int bufferFrames = sampleRate;
|
||||
audio.playback.buffer = ringbuffer_newUnbounded(bufferFrames,
|
||||
channels * sizeof(float));
|
||||
|
||||
audio.playback.deviceTiming = ringbuffer_new(16, sizeof(PlaybackDeviceTick));
|
||||
|
||||
lastChannels = channels;
|
||||
lastSampleRate = sampleRate;
|
||||
|
||||
audio.playback.channels = channels;
|
||||
audio.playback.sampleRate = sampleRate;
|
||||
audio.playback.stride = channels * sizeof(float);
|
||||
audio.playback.state = STREAM_STATE_SETUP_SPICE;
|
||||
|
||||
audio.playback.deviceData.periodFrames = 0;
|
||||
audio.playback.deviceData.nextPosition = 0;
|
||||
|
||||
audio.playback.spiceData.periodFrames = 0;
|
||||
audio.playback.spiceData.nextPosition = 0;
|
||||
audio.playback.spiceData.devPeriodFrames = 0;
|
||||
audio.playback.spiceData.devLastTime = INT64_MIN;
|
||||
audio.playback.spiceData.devNextTime = INT64_MIN;
|
||||
audio.playback.spiceData.offsetError = 0.0;
|
||||
audio.playback.spiceData.offsetErrorIntegral = 0.0;
|
||||
audio.playback.spiceData.ratioIntegral = 0.0;
|
||||
|
||||
int requestedPeriodFrames = max(g_params.audioPeriodSize, 1);
|
||||
audio.playback.deviceMaxPeriodFrames = 0;
|
||||
audio.playback.deviceStartFrames = 0;
|
||||
audio.audioDev->playback.setup(channels, sampleRate, requestedPeriodFrames,
|
||||
&audio.playback.deviceMaxPeriodFrames, &audio.playback.deviceStartFrames,
|
||||
playbackPullFrames);
|
||||
DEBUG_ASSERT(audio.playback.deviceMaxPeriodFrames > 0);
|
||||
|
||||
// if a volume level was stored, set it before we return
|
||||
if (audio.playback.volumeChannels)
|
||||
audio.audioDev->playback.volume(
|
||||
audio.playback.volumeChannels,
|
||||
audio.playback.volume);
|
||||
|
||||
// set the inital mute state
|
||||
if (audio.audioDev->playback.mute)
|
||||
audio.audioDev->playback.mute(audio.playback.mute);
|
||||
|
||||
// if the audio dev can report it's latency setup a timing graph
|
||||
audio.playback.timings = ringbuffer_new(1200, sizeof(float));
|
||||
audio.playback.graph = app_registerGraph("PLAYBACK",
|
||||
audio.playback.timings, 0.0f, 200.0f, audioGraphFormatFn);
|
||||
}
|
||||
|
||||
void audio_playbackStop(void)
|
||||
{
|
||||
if (!audio.audioDev)
|
||||
return;
|
||||
|
||||
switch (audio.playback.state)
|
||||
{
|
||||
case STREAM_STATE_RUN:
|
||||
{
|
||||
// Keep the audio device open for a while to reduce startup latency if
|
||||
// playback starts again
|
||||
audio.playback.state = STREAM_STATE_KEEP_ALIVE;
|
||||
|
||||
// Reset the resampler so it is safe to use for the next playback
|
||||
int error = src_reset(audio.playback.spiceData.src);
|
||||
if (error)
|
||||
{
|
||||
DEBUG_ERROR("Failed to reset resampler: %s", src_strerror(error));
|
||||
playbackStop();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case STREAM_STATE_SETUP_SPICE:
|
||||
case STREAM_STATE_SETUP_DEVICE:
|
||||
// Playback hasn't actually started yet so just clean up
|
||||
playbackStop();
|
||||
break;
|
||||
|
||||
case STREAM_STATE_KEEP_ALIVE:
|
||||
case STREAM_STATE_STOP:
|
||||
// Nothing to do
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void audio_playbackVolume(int channels, const uint16_t volume[])
|
||||
{
|
||||
if (!audio.audioDev || !audio.audioDev->playback.volume)
|
||||
return;
|
||||
|
||||
// store the values so we can restore the state if the stream is restarted
|
||||
channels = min(ARRAY_LENGTH(audio.playback.volume), channels);
|
||||
memcpy(audio.playback.volume, volume, sizeof(uint16_t) * channels);
|
||||
audio.playback.volumeChannels = channels;
|
||||
|
||||
if (!STREAM_ACTIVE(audio.playback.state))
|
||||
return;
|
||||
|
||||
audio.audioDev->playback.volume(channels, volume);
|
||||
}
|
||||
|
||||
void audio_playbackMute(bool mute)
|
||||
{
|
||||
if (!audio.audioDev || !audio.audioDev->playback.mute)
|
||||
return;
|
||||
|
||||
// store the value so we can restore it if the stream is restarted
|
||||
audio.playback.mute = mute;
|
||||
if (!STREAM_ACTIVE(audio.playback.state))
|
||||
return;
|
||||
|
||||
audio.audioDev->playback.mute(mute);
|
||||
}
|
||||
|
||||
static double computeDevicePosition(int64_t curTime)
|
||||
{
|
||||
// Interpolate to calculate the current device position
|
||||
PlaybackSpiceData * spiceData = &audio.playback.spiceData;
|
||||
return spiceData->devLastPosition +
|
||||
(spiceData->devNextPosition - spiceData->devLastPosition) *
|
||||
((double) (curTime - spiceData->devLastTime) /
|
||||
(spiceData->devNextTime - spiceData->devLastTime));
|
||||
}
|
||||
|
||||
void audio_playbackData(uint8_t * data, size_t size)
|
||||
{
|
||||
if (audio.playback.state == STREAM_STATE_STOP || !audio.audioDev || size == 0)
|
||||
return;
|
||||
|
||||
PlaybackSpiceData * spiceData = &audio.playback.spiceData;
|
||||
int64_t now = nanotime();
|
||||
|
||||
// Convert from s16 to f32 samples
|
||||
int spiceStride = audio.playback.channels * sizeof(int16_t);
|
||||
int frames = size / spiceStride;
|
||||
bool periodChanged = frames != spiceData->periodFrames;
|
||||
bool init = spiceData->periodFrames == 0;
|
||||
|
||||
if (periodChanged)
|
||||
{
|
||||
if (spiceData->framesIn)
|
||||
{
|
||||
free(spiceData->framesIn);
|
||||
free(spiceData->framesOut);
|
||||
}
|
||||
spiceData->periodFrames = frames;
|
||||
spiceData->framesIn = malloc(frames * audio.playback.stride);
|
||||
if (!spiceData->framesIn)
|
||||
{
|
||||
DEBUG_ERROR("Failed to malloc framesIn");
|
||||
playbackStop();
|
||||
return;
|
||||
}
|
||||
|
||||
spiceData->framesOutSize = round(frames * 1.1);
|
||||
spiceData->framesOut =
|
||||
malloc(spiceData->framesOutSize * audio.playback.stride);
|
||||
if (!spiceData->framesOut)
|
||||
{
|
||||
DEBUG_ERROR("Failed to malloc framesOut");
|
||||
playbackStop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
src_short_to_float_array((int16_t *) data, spiceData->framesIn,
|
||||
frames * audio.playback.channels);
|
||||
|
||||
// Receive timing information from the audio device thread
|
||||
PlaybackDeviceTick deviceTick;
|
||||
while (ringbuffer_consume(audio.playback.deviceTiming, &deviceTick, 1))
|
||||
{
|
||||
spiceData->devPeriodFrames = deviceTick.periodFrames;
|
||||
spiceData->devLastTime = spiceData->devNextTime;
|
||||
spiceData->devLastPosition = spiceData->devNextPosition;
|
||||
spiceData->devNextTime = deviceTick.nextTime;
|
||||
spiceData->devNextPosition = deviceTick.nextPosition;
|
||||
}
|
||||
|
||||
/* Determine the target latency. This is made up of the maximum audio device
|
||||
* period (plus a little extra to absorb timing jitter) and a configurable
|
||||
* additional buffer period. The default is set high enough to absorb typical
|
||||
* timing jitter from qemu. */
|
||||
int configLatencyMs = max(g_params.audioBufferLatency, 0);
|
||||
double targetLatencyFrames =
|
||||
audio.playback.deviceMaxPeriodFrames * 1.1 +
|
||||
configLatencyMs * audio.playback.sampleRate / 1000.0;
|
||||
|
||||
/* If the device is currently at a lower period size than its maximum (which
|
||||
* can happen, for example, if another application has requested a lower
|
||||
* latency) then we need to take that into account in our target latency.
|
||||
*
|
||||
* The reason to do this is not necessarily obvious, since we already set the
|
||||
* target latency based upon the maximum period size. The problem stems from
|
||||
* the way the device changes the period size. When the period size is
|
||||
* reduced, there will be a transitional period where `playbackPullFrames` is
|
||||
* invoked with the new smaller period size, but the time until the next
|
||||
* invocation is based upon the previous size. This happens because the device
|
||||
* is preparing the next small buffer while still playing back the previous
|
||||
* large buffer. The result of this is that we end up with a surplus of data
|
||||
* in the ring buffer. The overall latency is unchanged, but the balance has
|
||||
* shifted: there is more data in our ring buffer and less in the device
|
||||
* buffer.
|
||||
*
|
||||
* Unaccounted for, this would be detected as an offset error and playback
|
||||
* would be sped up to bring things back in line. In isolation, this is not
|
||||
* inherently problematic, and may even be desirable because it would reduce
|
||||
* the overall latency. The real problem occurs when the period size goes back
|
||||
* up.
|
||||
*
|
||||
* When the period size increases, the exact opposite happens. The device will
|
||||
* suddenly request data at the new period size, but the timing interval will
|
||||
* be based upon the previous period size during the transition. If there is
|
||||
* not enough data to satisfy this then playback will start severely
|
||||
* underrunning until the timing loop can correct for the error.
|
||||
*
|
||||
* To counteract this issue, if the current period size is smaller than the
|
||||
* maximum period size then we increase the target latency by the difference.
|
||||
* This keeps the offset error stable and ensures we have enough data in the
|
||||
* buffer to absorb rate increases. */
|
||||
if (spiceData->devPeriodFrames != 0 &&
|
||||
spiceData->devPeriodFrames < audio.playback.deviceMaxPeriodFrames)
|
||||
targetLatencyFrames +=
|
||||
audio.playback.deviceMaxPeriodFrames - spiceData->devPeriodFrames;
|
||||
|
||||
// Measure the Spice audio clock
|
||||
int64_t curTime;
|
||||
int64_t curPosition;
|
||||
double devPosition = DBL_MIN;
|
||||
if (periodChanged)
|
||||
{
|
||||
if (init)
|
||||
spiceData->nextTime = now;
|
||||
|
||||
curTime = spiceData->nextTime;
|
||||
curPosition = spiceData->nextPosition;
|
||||
|
||||
spiceData->periodSec = (double) frames / audio.playback.sampleRate;
|
||||
spiceData->nextTime += llrint(spiceData->periodSec * 1.0e9);
|
||||
|
||||
double bandwidth = 0.05;
|
||||
double omega = 2.0 * M_PI * bandwidth * spiceData->periodSec;
|
||||
spiceData->b = M_SQRT2 * omega;
|
||||
spiceData->c = omega * omega;
|
||||
}
|
||||
else
|
||||
{
|
||||
double error = (now - spiceData->nextTime) * 1.0e-9;
|
||||
if (fabs(error) >= 0.2 || audio.playback.state == STREAM_STATE_KEEP_ALIVE)
|
||||
{
|
||||
/* Clock error is too high or we are starting a new playback; slew the
|
||||
* write pointer and reset the timing parameters to get back in sync. If
|
||||
* we know the device playback position then we can slew directly to the
|
||||
* target latency, otherwise just slew based upon the error amount */
|
||||
int slewFrames;
|
||||
if (spiceData->devLastTime != INT64_MIN)
|
||||
{
|
||||
devPosition = computeDevicePosition(now);
|
||||
double targetPosition = devPosition + targetLatencyFrames;
|
||||
|
||||
// If starting a new playback we need to allow a little extra time for
|
||||
// the resampler startup latency
|
||||
if (audio.playback.state == STREAM_STATE_KEEP_ALIVE)
|
||||
{
|
||||
int resamplerLatencyFrames = 20;
|
||||
targetPosition += resamplerLatencyFrames;
|
||||
}
|
||||
|
||||
slewFrames = round(targetPosition - spiceData->nextPosition);
|
||||
}
|
||||
else
|
||||
slewFrames = round(error * audio.playback.sampleRate);
|
||||
|
||||
ringbuffer_append(audio.playback.buffer, NULL, slewFrames);
|
||||
|
||||
curTime = now;
|
||||
curPosition = spiceData->nextPosition + slewFrames;
|
||||
|
||||
spiceData->periodSec = (double) frames / audio.playback.sampleRate;
|
||||
spiceData->nextTime = now + llrint(spiceData->periodSec * 1.0e9);
|
||||
spiceData->nextPosition = curPosition;
|
||||
|
||||
spiceData->offsetError = 0.0;
|
||||
spiceData->offsetErrorIntegral = 0.0;
|
||||
spiceData->ratioIntegral = 0.0;
|
||||
|
||||
audio.playback.state = STREAM_STATE_RUN;
|
||||
}
|
||||
else
|
||||
{
|
||||
curTime = spiceData->nextTime;
|
||||
curPosition = spiceData->nextPosition;
|
||||
|
||||
spiceData->nextTime +=
|
||||
llrint((spiceData->b * error + spiceData->periodSec) * 1.0e9);
|
||||
spiceData->periodSec += spiceData->c * error;
|
||||
}
|
||||
}
|
||||
|
||||
/* Measure the offset between the Spice position and the device position,
|
||||
* and how far away this is from the target latency. We use this to adjust
|
||||
* the playback speed to bring them back in line. This value can change
|
||||
* quite rapidly, particularly at the start of playback, so filter it to
|
||||
* avoid sudden pitch shifts which will be noticeable to the user. */
|
||||
double actualOffset = 0.0;
|
||||
double offsetError = spiceData->offsetError;
|
||||
if (spiceData->devLastTime != INT64_MIN)
|
||||
{
|
||||
if (devPosition == DBL_MIN)
|
||||
devPosition = computeDevicePosition(curTime);
|
||||
|
||||
actualOffset = curPosition - devPosition;
|
||||
double actualOffsetError = -(actualOffset - targetLatencyFrames);
|
||||
|
||||
double error = actualOffsetError - offsetError;
|
||||
spiceData->offsetError += spiceData->b * error +
|
||||
spiceData->offsetErrorIntegral;
|
||||
spiceData->offsetErrorIntegral += spiceData->c * error;
|
||||
}
|
||||
|
||||
// Resample the audio to adjust the playback speed. Use a PI controller to
|
||||
// adjust the resampling ratio based upon the measured offset
|
||||
double kp = 0.5e-6;
|
||||
double ki = 1.0e-16;
|
||||
|
||||
spiceData->ratioIntegral += offsetError * spiceData->periodSec;
|
||||
|
||||
double piOutput = kp * offsetError + ki * spiceData->ratioIntegral;
|
||||
double ratio = 1.0 + piOutput;
|
||||
|
||||
int consumed = 0;
|
||||
while (consumed < frames)
|
||||
{
|
||||
SRC_DATA srcData =
|
||||
{
|
||||
.data_in = spiceData->framesIn +
|
||||
consumed * audio.playback.channels,
|
||||
.data_out = spiceData->framesOut,
|
||||
.input_frames = frames - consumed,
|
||||
.output_frames = spiceData->framesOutSize,
|
||||
.input_frames_used = 0,
|
||||
.output_frames_gen = 0,
|
||||
.end_of_input = 0,
|
||||
.src_ratio = ratio
|
||||
};
|
||||
|
||||
int error = src_process(spiceData->src, &srcData);
|
||||
if (error)
|
||||
{
|
||||
DEBUG_ERROR("Resampling failed: %s", src_strerror(error));
|
||||
return;
|
||||
}
|
||||
|
||||
ringbuffer_append(audio.playback.buffer, spiceData->framesOut,
|
||||
srcData.output_frames_gen);
|
||||
|
||||
consumed += srcData.input_frames_used;
|
||||
spiceData->nextPosition += srcData.output_frames_gen;
|
||||
}
|
||||
|
||||
if (audio.playback.state == STREAM_STATE_SETUP_SPICE)
|
||||
{
|
||||
/* Latency corrections at startup can be quite significant due to poor
|
||||
* packet pacing from Spice, so require at least two full Spice periods'
|
||||
* worth of data in addition to the startup delay requested by the device
|
||||
* before starting playback to minimise the chances of underrunning. */
|
||||
int startFrames =
|
||||
spiceData->periodFrames * 2 + audio.playback.deviceStartFrames;
|
||||
audio.playback.targetStartFrames = startFrames;
|
||||
|
||||
/* The actual time between opening the device and the device starting to
|
||||
* pull data can range anywhere between nearly instant and hundreds of
|
||||
* milliseconds. To minimise startup latency, we open the device
|
||||
* immediately. If the device starts earlier than required (as per the
|
||||
* `startFrames` value we just calculated), then a period of silence will be
|
||||
* inserted at the beginning of playback to avoid underrunning. If it starts
|
||||
* later, then we just accept the higher latency and let the adaptive
|
||||
* resampling deal with it. */
|
||||
audio.playback.state = STREAM_STATE_SETUP_DEVICE;
|
||||
audio.audioDev->playback.start();
|
||||
}
|
||||
|
||||
double latencyFrames = actualOffset;
|
||||
if (audio.audioDev->playback.latency)
|
||||
latencyFrames += audio.audioDev->playback.latency();
|
||||
|
||||
const float latency = latencyFrames * 1000.0 / audio.playback.sampleRate;
|
||||
ringbuffer_push(audio.playback.timings, &latency);
|
||||
app_invalidateGraph(audio.playback.graph);
|
||||
}
|
||||
|
||||
bool audio_supportsRecord(void)
|
||||
{
|
||||
return audio.audioDev && audio.audioDev->record.start;
|
||||
}
|
||||
|
||||
static void recordPushFrames(uint8_t * data, int frames)
|
||||
{
|
||||
purespice_writeAudio(data, frames * audio.record.stride, 0);
|
||||
}
|
||||
|
||||
static void realRecordStart(int channels, int sampleRate, PSAudioFormat format)
|
||||
{
|
||||
audio.record.started = true;
|
||||
audio.record.stride = channels * sizeof(uint16_t);
|
||||
|
||||
audio.audioDev->record.start(channels, sampleRate, recordPushFrames);
|
||||
|
||||
// if a volume level was stored, set it before we return
|
||||
if (audio.record.volumeChannels)
|
||||
audio.audioDev->record.volume(
|
||||
audio.playback.volumeChannels,
|
||||
audio.playback.volume);
|
||||
|
||||
// set the inital mute state
|
||||
if (audio.audioDev->record.mute)
|
||||
audio.audioDev->record.mute(audio.playback.mute);
|
||||
|
||||
if (g_params.micShowIndicator)
|
||||
app_showRecord(true);
|
||||
}
|
||||
|
||||
struct AudioFormat
|
||||
{
|
||||
int channels;
|
||||
int sampleRate;
|
||||
PSAudioFormat format;
|
||||
};
|
||||
|
||||
static void recordConfirm(bool yes, void * opaque)
|
||||
{
|
||||
if (yes)
|
||||
{
|
||||
DEBUG_INFO("Microphone access granted");
|
||||
realRecordStart(
|
||||
audio.record.confirmChannels,
|
||||
audio.record.confirmSampleRate,
|
||||
audio.record.confirmFormat
|
||||
);
|
||||
}
|
||||
else
|
||||
DEBUG_INFO("Microphone access denied");
|
||||
|
||||
audio.record.confirmHandle = NULL;
|
||||
}
|
||||
|
||||
void audio_recordStart(int channels, int sampleRate, PSAudioFormat format)
|
||||
{
|
||||
if (!audio.audioDev)
|
||||
return;
|
||||
|
||||
static int lastChannels = 0;
|
||||
static int lastSampleRate = 0;
|
||||
|
||||
if (audio.record.started)
|
||||
{
|
||||
if (channels != lastChannels || sampleRate != lastSampleRate)
|
||||
audio.audioDev->record.stop();
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
lastChannels = channels;
|
||||
lastSampleRate = sampleRate;
|
||||
|
||||
if (audio.record.started)
|
||||
realRecordStart(channels, sampleRate, format);
|
||||
else if (g_params.micAlwaysAllow)
|
||||
{
|
||||
DEBUG_INFO("Microphone access granted by default");
|
||||
realRecordStart(channels, sampleRate, format);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (audio.record.confirmHandle)
|
||||
app_msgBoxClose(audio.record.confirmHandle);
|
||||
|
||||
audio.record.confirmChannels = channels;
|
||||
audio.record.confirmSampleRate = sampleRate;
|
||||
audio.record.confirmFormat = format;
|
||||
audio.record.confirmHandle = app_confirmMsgBox(
|
||||
"Microphone", recordConfirm, NULL,
|
||||
"An application just opened the microphone!\n"
|
||||
"Do you want it to access your microphone?");
|
||||
}
|
||||
}
|
||||
|
||||
void audio_recordStop(void)
|
||||
{
|
||||
if (!audio.audioDev || !audio.record.started)
|
||||
return;
|
||||
|
||||
DEBUG_INFO("Microphone recording stopped");
|
||||
audio.audioDev->record.stop();
|
||||
audio.record.started = false;
|
||||
|
||||
if (g_params.micShowIndicator)
|
||||
app_showRecord(false);
|
||||
}
|
||||
|
||||
void audio_recordVolume(int channels, const uint16_t volume[])
|
||||
{
|
||||
if (!audio.audioDev || !audio.audioDev->record.volume)
|
||||
return;
|
||||
|
||||
// store the values so we can restore the state if the stream is restarted
|
||||
channels = min(ARRAY_LENGTH(audio.record.volume), channels);
|
||||
memcpy(audio.record.volume, volume, sizeof(uint16_t) * channels);
|
||||
audio.record.volumeChannels = channels;
|
||||
|
||||
if (!audio.record.started)
|
||||
return;
|
||||
|
||||
audio.audioDev->record.volume(channels, volume);
|
||||
}
|
||||
|
||||
void audio_recordMute(bool mute)
|
||||
{
|
||||
if (!audio.audioDev || !audio.audioDev->record.mute)
|
||||
return;
|
||||
|
||||
// store the value so we can restore it if the stream is restarted
|
||||
audio.record.mute = mute;
|
||||
if (!audio.record.started)
|
||||
return;
|
||||
|
||||
audio.audioDev->record.mute(mute);
|
||||
}
|
||||
|
||||
#endif
|
||||
48
client/src/audio.h
Normal file
48
client/src/audio.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2022 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
|
||||
*/
|
||||
|
||||
#if ENABLE_AUDIO
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <purespice.h>
|
||||
|
||||
void audio_init(void);
|
||||
void audio_free(void);
|
||||
|
||||
bool audio_supportsPlayback(void);
|
||||
void audio_playbackStart(int channels, int sampleRate, PSAudioFormat format,
|
||||
uint32_t time);
|
||||
void audio_playbackStop(void);
|
||||
void audio_playbackVolume(int channels, const uint16_t volume[]);
|
||||
void audio_playbackMute(bool mute);
|
||||
void audio_playbackData(uint8_t * data, size_t size);
|
||||
|
||||
bool audio_supportsRecord(void);
|
||||
void audio_recordStart(int channels, int sampleRate, PSAudioFormat format);
|
||||
void audio_recordStop(void);
|
||||
void audio_recordVolume(int channels, const uint16_t volume[]);
|
||||
void audio_recordMute(bool mute);
|
||||
|
||||
#else
|
||||
|
||||
static inline void audio_init(void) {}
|
||||
static inline void audio_free(void) {}
|
||||
|
||||
#endif
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -21,11 +21,10 @@
|
||||
#include "clipboard.h"
|
||||
|
||||
#include "main.h"
|
||||
#include "ll.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
LG_ClipboardData cb_spiceTypeToLGType(const SpiceDataType type)
|
||||
LG_ClipboardData cb_spiceTypeToLGType(const PSDataType type)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
@@ -40,7 +39,7 @@ LG_ClipboardData cb_spiceTypeToLGType(const SpiceDataType type)
|
||||
}
|
||||
}
|
||||
|
||||
SpiceDataType cb_lgTypeToSpiceType(const LG_ClipboardData type)
|
||||
PSDataType cb_lgTypeToSpiceType(const LG_ClipboardData type)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
@@ -55,7 +54,7 @@ SpiceDataType cb_lgTypeToSpiceType(const LG_ClipboardData type)
|
||||
}
|
||||
}
|
||||
|
||||
void cb_spiceNotice(const SpiceDataType type)
|
||||
void cb_spiceNotice(const PSDataType type)
|
||||
{
|
||||
if (!g_params.clipboardToLocal)
|
||||
return;
|
||||
@@ -67,7 +66,7 @@ void cb_spiceNotice(const SpiceDataType type)
|
||||
g_state.ds->cbNotice(cb_spiceTypeToLGType(type));
|
||||
}
|
||||
|
||||
void cb_spiceData(const SpiceDataType type, uint8_t * buffer, uint32_t size)
|
||||
void cb_spiceData(const PSDataType type, uint8_t * buffer, uint32_t size)
|
||||
{
|
||||
if (!g_params.clipboardToLocal)
|
||||
return;
|
||||
@@ -107,7 +106,7 @@ void cb_spiceRelease(void)
|
||||
g_state.ds->cbRelease();
|
||||
}
|
||||
|
||||
void cb_spiceRequest(const SpiceDataType type)
|
||||
void cb_spiceRequest(const PSDataType type)
|
||||
{
|
||||
if (!g_params.clipboardToVM)
|
||||
return;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -18,13 +18,13 @@
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "spice/spice.h"
|
||||
#include <purespice.h>
|
||||
#include "interface/displayserver.h"
|
||||
|
||||
LG_ClipboardData cb_spiceTypeToLGType(const SpiceDataType type);
|
||||
SpiceDataType cb_lgTypeToSpiceType(const LG_ClipboardData type);
|
||||
LG_ClipboardData cb_spiceTypeToLGType(const PSDataType type);
|
||||
PSDataType cb_lgTypeToSpiceType(const LG_ClipboardData type);
|
||||
|
||||
void cb_spiceNotice(const SpiceDataType type);
|
||||
void cb_spiceData(const SpiceDataType type, uint8_t * buffer, uint32_t size);
|
||||
void cb_spiceNotice(const PSDataType type);
|
||||
void cb_spiceData(const PSDataType type, uint8_t * buffer, uint32_t size);
|
||||
void cb_spiceRelease(void);
|
||||
void cb_spiceRequest(const SpiceDataType type);
|
||||
void cb_spiceRequest(const PSDataType type);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -46,7 +46,7 @@ static bool optScancodeValidate(struct Option * opt, const char ** error);
|
||||
static char * optScancodeToString(struct Option * opt);
|
||||
static bool optRotateValidate (struct Option * opt, const char ** error);
|
||||
|
||||
static void doLicense();
|
||||
static void doLicense(void);
|
||||
|
||||
static struct Option options[] =
|
||||
{
|
||||
@@ -163,6 +163,13 @@ static struct Option options[] =
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false,
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "intUpscale",
|
||||
.description = "Allow only integer upscaling",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false,
|
||||
},
|
||||
{
|
||||
.module = "win",
|
||||
.name = "shrinkOnUpscale",
|
||||
@@ -428,6 +435,13 @@ static struct Option options[] =
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true
|
||||
},
|
||||
{
|
||||
.module = "spice",
|
||||
.name = "audio",
|
||||
.description = "Enable SPICE audio support",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true
|
||||
},
|
||||
{
|
||||
.module = "spice",
|
||||
.name = "scaleCursor",
|
||||
@@ -457,6 +471,36 @@ static struct Option options[] =
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true
|
||||
},
|
||||
|
||||
// audio options
|
||||
{
|
||||
.module = "audio",
|
||||
.name = "periodSize",
|
||||
.description = "Requested audio device period size in samples",
|
||||
.type = OPTION_TYPE_INT,
|
||||
.value.x_int = 2048
|
||||
},
|
||||
{
|
||||
.module = "audio",
|
||||
.name = "bufferLatency",
|
||||
.description = "Additional buffer latency in milliseconds",
|
||||
.type = OPTION_TYPE_INT,
|
||||
.value.x_int = 13
|
||||
},
|
||||
{
|
||||
.module = "audio",
|
||||
.name = "micAlwaysAllow",
|
||||
.description = "Always allow guest attempts to access the microphone",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = false
|
||||
},
|
||||
{
|
||||
.module = "audio",
|
||||
.name = "micShowIndicator",
|
||||
.description = "Display microphone usage indicator",
|
||||
.type = OPTION_TYPE_BOOL,
|
||||
.value.x_bool = true
|
||||
},
|
||||
{0}
|
||||
};
|
||||
|
||||
@@ -482,22 +526,38 @@ bool config_load(int argc, char * argv[])
|
||||
|
||||
// load config from user's home directory
|
||||
struct passwd * pw = getpwuid(getuid());
|
||||
char * localFile;
|
||||
alloc_sprintf(&localFile, "%s/.looking-glass-client.ini", pw->pw_dir);
|
||||
if (stat(localFile, &st) >= 0 && S_ISREG(st.st_mode))
|
||||
if (!pw)
|
||||
DEBUG_WARN("getpwuid failed, unable to load user configuration");
|
||||
else
|
||||
{
|
||||
DEBUG_INFO("Loading config from: %s", localFile);
|
||||
if (!option_load(localFile))
|
||||
char * localFile;
|
||||
alloc_sprintf(&localFile, "%s/.looking-glass-client.ini", pw->pw_dir);
|
||||
if (!localFile)
|
||||
{
|
||||
free(localFile);
|
||||
DEBUG_ERROR("out of memory");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (stat(localFile, &st) >= 0 && S_ISREG(st.st_mode))
|
||||
{
|
||||
DEBUG_INFO("Loading config from: %s", localFile);
|
||||
if (!option_load(localFile))
|
||||
{
|
||||
free(localFile);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
free(localFile);
|
||||
}
|
||||
free(localFile);
|
||||
|
||||
// load config from XDG_CONFIG_HOME
|
||||
char * xdgFile;
|
||||
alloc_sprintf(&xdgFile, "%s/client.ini", lgConfigDir());
|
||||
if (!xdgFile)
|
||||
{
|
||||
DEBUG_ERROR("out of memory");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (xdgFile && stat(xdgFile, &st) >= 0 && S_ISREG(st.st_mode))
|
||||
{
|
||||
@@ -550,6 +610,7 @@ bool config_load(int argc, char * argv[])
|
||||
g_params.keepAspect = option_get_bool ("win", "keepAspect" );
|
||||
g_params.forceAspect = option_get_bool ("win", "forceAspect" );
|
||||
g_params.dontUpscale = option_get_bool ("win", "dontUpscale" );
|
||||
g_params.intUpscale = option_get_bool ("win", "intUpscale" );
|
||||
g_params.shrinkOnUpscale = option_get_bool ("win", "shrinkOnUpscale");
|
||||
g_params.borderless = option_get_bool ("win", "borderless" );
|
||||
g_params.fullscreen = option_get_bool ("win", "fullScreen" );
|
||||
@@ -609,6 +670,7 @@ bool config_load(int argc, char * argv[])
|
||||
|
||||
g_params.useSpiceInput = option_get_bool("spice", "input" );
|
||||
g_params.useSpiceClipboard = option_get_bool("spice", "clipboard");
|
||||
g_params.useSpiceAudio = option_get_bool("spice", "audio" );
|
||||
|
||||
if (g_params.useSpiceClipboard)
|
||||
{
|
||||
@@ -628,6 +690,11 @@ bool config_load(int argc, char * argv[])
|
||||
g_params.showCursorDot = option_get_bool("spice", "showCursorDot");
|
||||
}
|
||||
|
||||
g_params.audioPeriodSize = option_get_int("audio", "periodSize");
|
||||
g_params.audioBufferLatency = option_get_int("audio", "bufferLatency");
|
||||
g_params.micAlwaysAllow = option_get_bool("audio", "micAlwaysAllow");
|
||||
g_params.micShowIndicator = option_get_bool("audio", "micShowIndicator");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -642,7 +709,7 @@ static void doLicense(void)
|
||||
// BEGIN LICENSE BLOCK
|
||||
"\n"
|
||||
"Looking Glass\n"
|
||||
"Copyright © 2017-2021 The Looking Glass Authors\n"
|
||||
"Copyright © 2017-2022 The Looking Glass Authors\n"
|
||||
"https://looking-glass.io\n"
|
||||
"\n"
|
||||
"This program is free software; you can redistribute it and/or modify it under\n"
|
||||
@@ -687,6 +754,8 @@ static bool optRendererParse(struct Option * opt, const char * str)
|
||||
static StringList optRendererValues(struct Option * opt)
|
||||
{
|
||||
StringList sl = stringlist_new(false);
|
||||
if (!sl)
|
||||
return NULL;
|
||||
|
||||
// this typecast is safe as the stringlist doesn't own the values
|
||||
for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i)
|
||||
@@ -729,6 +798,9 @@ static bool optPosParse(struct Option * opt, const char * str)
|
||||
static StringList optPosValues(struct Option * opt)
|
||||
{
|
||||
StringList sl = stringlist_new(false);
|
||||
if (!sl)
|
||||
return NULL;
|
||||
|
||||
stringlist_push(sl, "center");
|
||||
stringlist_push(sl, "<left>x<top>, e.g. 100x100");
|
||||
return sl;
|
||||
@@ -741,6 +813,11 @@ static char * optPosToString(struct Option * opt)
|
||||
|
||||
int len = snprintf(NULL, 0, "%dx%d", g_params.x, g_params.y);
|
||||
char * str = malloc(len + 1);
|
||||
if (!str)
|
||||
{
|
||||
DEBUG_ERROR("out of memory");
|
||||
return NULL;
|
||||
}
|
||||
sprintf(str, "%dx%d", g_params.x, g_params.y);
|
||||
|
||||
return str;
|
||||
@@ -764,6 +841,9 @@ static bool optSizeParse(struct Option * opt, const char * str)
|
||||
static StringList optSizeValues(struct Option * opt)
|
||||
{
|
||||
StringList sl = stringlist_new(false);
|
||||
if (!sl)
|
||||
return NULL;
|
||||
|
||||
stringlist_push(sl, "<left>x<top>, e.g. 100x100");
|
||||
return sl;
|
||||
}
|
||||
@@ -772,6 +852,11 @@ static char * optSizeToString(struct Option * opt)
|
||||
{
|
||||
int len = snprintf(NULL, 0, "%ux%u", g_params.w, g_params.h);
|
||||
char * str = malloc(len + 1);
|
||||
if (!str)
|
||||
{
|
||||
DEBUG_ERROR("out of memory");
|
||||
return NULL;
|
||||
}
|
||||
sprintf(str, "%ux%u", g_params.w, g_params.h);
|
||||
|
||||
return str;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -20,6 +20,6 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
void config_init();
|
||||
void config_init(void);
|
||||
bool config_load(int argc, char * argv[]);
|
||||
void config_free();
|
||||
void config_free(void);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -31,39 +31,45 @@
|
||||
|
||||
#define RESIZE_TIMEOUT (10 * 1000) // 10ms
|
||||
|
||||
static bool isInView(void)
|
||||
{
|
||||
return
|
||||
g_cursor.pos.x >= g_state.dstRect.x &&
|
||||
g_cursor.pos.x < g_state.dstRect.x + g_state.dstRect.w &&
|
||||
g_cursor.pos.y >= g_state.dstRect.y &&
|
||||
g_cursor.pos.y < g_state.dstRect.y + g_state.dstRect.h;
|
||||
}
|
||||
|
||||
bool core_inputEnabled(void)
|
||||
{
|
||||
return g_params.useSpiceInput && !g_state.ignoreInput &&
|
||||
((g_cursor.grab && g_params.captureInputOnly) || !g_params.captureInputOnly);
|
||||
}
|
||||
|
||||
void core_setCursorInView(bool enable)
|
||||
void core_invalidatePointer(bool detectInView)
|
||||
{
|
||||
// if the state has not changed, don't do anything else
|
||||
if (g_cursor.inView == enable)
|
||||
return;
|
||||
|
||||
if (enable && !g_state.focused)
|
||||
return;
|
||||
|
||||
// do not allow the view to become active if any mouse buttons are being held,
|
||||
// this fixes issues with meta window resizing.
|
||||
if (enable && g_cursor.buttons)
|
||||
return;
|
||||
|
||||
g_cursor.inView = enable;
|
||||
g_cursor.draw = (g_params.alwaysShowCursor || g_params.captureInputOnly)
|
||||
? true : enable;
|
||||
g_cursor.redraw = true;
|
||||
|
||||
/* if the display server does not support warp, then we can not operate in
|
||||
* always relative mode and we should not grab the pointer */
|
||||
enum LG_DSWarpSupport warpSupport = LG_DS_WARP_NONE;
|
||||
app_getProp(LG_DS_WARP_SUPPORT, &warpSupport);
|
||||
|
||||
g_cursor.warpState = enable ? WARP_STATE_ON : WARP_STATE_OFF;
|
||||
if (detectInView)
|
||||
{
|
||||
bool inView = isInView();
|
||||
// do not allow the view to become active if any mouse buttons are being held,
|
||||
// this fixes issues with meta window resizing.
|
||||
if (inView && g_cursor.buttons)
|
||||
return;
|
||||
|
||||
if (enable)
|
||||
g_cursor.inView = inView;
|
||||
}
|
||||
|
||||
g_cursor.draw = (g_params.alwaysShowCursor || g_params.captureInputOnly)
|
||||
? true : g_cursor.inView;
|
||||
g_cursor.redraw = true;
|
||||
|
||||
g_cursor.warpState = g_cursor.inView ? WARP_STATE_ON : WARP_STATE_OFF;
|
||||
if (g_cursor.inView)
|
||||
{
|
||||
if (g_params.hideMouse)
|
||||
g_state.ds->setPointer(LG_POINTER_NONE);
|
||||
@@ -88,6 +94,19 @@ void core_setCursorInView(bool enable)
|
||||
g_cursor.warpState = WARP_STATE_ON;
|
||||
}
|
||||
|
||||
void core_setCursorInView(bool enable)
|
||||
{
|
||||
// if the state has not changed, don't do anything else
|
||||
if (g_cursor.inView == enable)
|
||||
return;
|
||||
|
||||
if (enable && !g_state.focused)
|
||||
return;
|
||||
|
||||
g_cursor.inView = enable;
|
||||
core_invalidatePointer(false);
|
||||
}
|
||||
|
||||
void core_setGrab(bool enable)
|
||||
{
|
||||
core_setGrabQuiet(enable);
|
||||
@@ -147,7 +166,7 @@ void core_setGrabQuiet(bool enable)
|
||||
bool core_warpPointer(int x, int y, bool exiting)
|
||||
{
|
||||
if ((!g_cursor.inWindow && !exiting) ||
|
||||
g_state.overlayInput ||
|
||||
app_isOverlayMode() ||
|
||||
g_cursor.warpState == WARP_STATE_OFF)
|
||||
return false;
|
||||
|
||||
@@ -204,6 +223,20 @@ void core_updatePositionInfo(void)
|
||||
g_state.dstRect.y = g_state.windowCY - srcH / 2;
|
||||
}
|
||||
else
|
||||
if (g_params.intUpscale &&
|
||||
srcW <= g_state.windowW &&
|
||||
srcH <= g_state.windowH)
|
||||
{
|
||||
force = false;
|
||||
const int scale = min(
|
||||
floor(g_state.windowW / srcW),
|
||||
floor(g_state.windowH / srcH));
|
||||
g_state.dstRect.w = srcW * scale;
|
||||
g_state.dstRect.h = srcH * scale;
|
||||
g_state.dstRect.x = g_state.windowCX - g_state.dstRect.w / 2;
|
||||
g_state.dstRect.y = g_state.windowCY - g_state.dstRect.h / 2;
|
||||
}
|
||||
else
|
||||
if ((int)(wndAspect * 1000) == (int)(srcAspect * 1000))
|
||||
{
|
||||
force = false;
|
||||
@@ -357,7 +390,7 @@ void core_handleGuestMouseUpdate(void)
|
||||
if (!util_guestCurToLocal(&localPos))
|
||||
return;
|
||||
|
||||
if (g_state.overlayInput || !g_cursor.inView)
|
||||
if (app_isOverlayMode() || !g_cursor.inView)
|
||||
return;
|
||||
|
||||
g_state.ds->guestPointerUpdated(
|
||||
@@ -392,25 +425,19 @@ void core_handleMouseGrabbed(double ex, double ey)
|
||||
if (x == 0 && y == 0)
|
||||
return;
|
||||
|
||||
if (!spice_mouse_motion(x, y))
|
||||
if (!purespice_mouseMotion(x, y))
|
||||
DEBUG_ERROR("failed to send mouse motion message");
|
||||
}
|
||||
|
||||
static bool isInView(void)
|
||||
{
|
||||
return
|
||||
g_cursor.pos.x >= g_state.dstRect.x &&
|
||||
g_cursor.pos.x < g_state.dstRect.x + g_state.dstRect.w &&
|
||||
g_cursor.pos.y >= g_state.dstRect.y &&
|
||||
g_cursor.pos.y < g_state.dstRect.y + g_state.dstRect.h;
|
||||
}
|
||||
|
||||
void core_handleMouseNormal(double ex, double ey)
|
||||
{
|
||||
// prevent cursor handling outside of capture if the position is not known
|
||||
if (!g_cursor.guest.valid)
|
||||
return;
|
||||
|
||||
if (g_cursor.realigning)
|
||||
return;
|
||||
|
||||
if (!core_inputEnabled())
|
||||
return;
|
||||
|
||||
@@ -448,7 +475,8 @@ void core_handleMouseNormal(double ex, double ey)
|
||||
struct DoublePoint guest;
|
||||
util_localCurToGuest(&guest);
|
||||
|
||||
if (g_state.kvmfrFeatures & KVMFR_FEATURE_SETCURSORPOS)
|
||||
if (!g_state.stopVideo &&
|
||||
g_state.kvmfrFeatures & KVMFR_FEATURE_SETCURSORPOS)
|
||||
{
|
||||
const KVMFRSetCursorPos msg = {
|
||||
.msg.type = KVMFR_MESSAGE_SETCURSORPOS,
|
||||
@@ -457,15 +485,36 @@ void core_handleMouseNormal(double ex, double ey)
|
||||
};
|
||||
|
||||
uint32_t setPosSerial;
|
||||
if (lgmpClientSendData(g_state.pointerQueue,
|
||||
&msg, sizeof(msg), &setPosSerial) == LGMP_OK)
|
||||
LGMP_STATUS status;
|
||||
if ((status = lgmpClientSendData(g_state.pointerQueue,
|
||||
&msg, sizeof(msg), &setPosSerial)) != LGMP_OK)
|
||||
{
|
||||
DEBUG_WARN("Message send failed: %s", lgmpStatusString(status));
|
||||
goto fallback;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* wait for the move request to be processed */
|
||||
g_cursor.realigning = true;
|
||||
do
|
||||
{
|
||||
LG_LOCK(g_state.pointerQueueLock);
|
||||
if (!g_state.pointerQueue)
|
||||
{
|
||||
/* the queue is nolonger valid, assume complete */
|
||||
g_cursor.realigning = false;
|
||||
LG_UNLOCK(g_state.pointerQueueLock);
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t hostSerial;
|
||||
if (lgmpClientGetSerial(g_state.pointerQueue, &hostSerial) != LGMP_OK)
|
||||
{
|
||||
g_cursor.realigning = false;
|
||||
LG_UNLOCK(g_state.pointerQueueLock);
|
||||
return;
|
||||
}
|
||||
LG_UNLOCK(g_state.pointerQueueLock);
|
||||
|
||||
if (hostSerial >= setPosSerial)
|
||||
break;
|
||||
@@ -474,9 +523,11 @@ void core_handleMouseNormal(double ex, double ey)
|
||||
}
|
||||
while(app_isRunning());
|
||||
|
||||
g_cursor.guest.x = msg.x;
|
||||
g_cursor.guest.y = msg.y;
|
||||
g_cursor.realign = false;
|
||||
g_cursor.guest.x = msg.x;
|
||||
g_cursor.guest.y = msg.y;
|
||||
g_cursor.realign = false;
|
||||
g_cursor.realigning = false;
|
||||
g_cursor.redraw = true;
|
||||
|
||||
if (!g_cursor.inWindow)
|
||||
return;
|
||||
@@ -487,6 +538,7 @@ void core_handleMouseNormal(double ex, double ey)
|
||||
}
|
||||
else
|
||||
{
|
||||
fallback:
|
||||
/* add the difference to the offset */
|
||||
ex += guest.x - (g_cursor.guest.x + g_cursor.guest.hx);
|
||||
ey += guest.y - (g_cursor.guest.y + g_cursor.guest.hy);
|
||||
@@ -601,7 +653,7 @@ void core_handleMouseNormal(double ex, double ey)
|
||||
g_cursor.guest.y += y;
|
||||
}
|
||||
|
||||
if (!spice_mouse_motion(x, y))
|
||||
if (!purespice_mouseMotion(x, y))
|
||||
DEBUG_ERROR("failed to send mouse motion message");
|
||||
}
|
||||
|
||||
@@ -613,3 +665,34 @@ void core_resetOverlayInputState(void)
|
||||
for(int key = 0; key < ARRAY_LENGTH(g_state.io->KeysDown); key++)
|
||||
g_state.io->KeysDown[key] = false;
|
||||
}
|
||||
|
||||
void core_updateOverlayState(void)
|
||||
{
|
||||
g_state.cursorLast = -2;
|
||||
|
||||
static bool wasGrabbed = false;
|
||||
if (app_isOverlayMode())
|
||||
{
|
||||
wasGrabbed = g_cursor.grab;
|
||||
|
||||
g_state.io->ConfigFlags &= ~ImGuiConfigFlags_NoMouse;
|
||||
g_state.io->MousePos = (ImVec2) { g_cursor.pos.x, g_cursor.pos.y };
|
||||
|
||||
core_setGrabQuiet(false);
|
||||
core_setCursorInView(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_state.io->ConfigFlags |= ImGuiConfigFlags_NoMouse;
|
||||
core_resetOverlayInputState();
|
||||
core_setGrabQuiet(wasGrabbed);
|
||||
core_invalidatePointer(true);
|
||||
app_invalidateWindow(false);
|
||||
|
||||
if (!g_cursor.grab)
|
||||
{
|
||||
g_cursor.realign = true;
|
||||
core_handleMouseNormal(0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
bool core_inputEnabled(void);
|
||||
void core_invalidatePointer(bool detectInView);
|
||||
void core_setCursorInView(bool enable);
|
||||
void core_setGrab(bool enable);
|
||||
void core_setGrabQuiet(bool enable);
|
||||
@@ -39,5 +40,6 @@ void core_handleGuestMouseUpdate(void);
|
||||
void core_handleMouseGrabbed(double ex, double ey);
|
||||
void core_handleMouseNormal(double ex, double ey);
|
||||
void core_resetOverlayInputState(void);
|
||||
void core_updateOverlayState(void);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2021 The Looking Glass Authors
|
||||
* Copyright © 2017-2022 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user