mirror of
https://github.com/Genymobile/scrcpy.git
synced 2026-02-23 23:04:43 +01:00
Compare commits
103 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0025ef185 | ||
|
|
67bb26d62a | ||
|
|
34df0b476a | ||
|
|
6072409048 | ||
|
|
d16c9e3a26 | ||
|
|
f6dd00540e | ||
|
|
1d2d7d7965 | ||
|
|
79ab94c5a9 | ||
|
|
ae47396e27 | ||
|
|
61fa8cefd6 | ||
|
|
808900468b | ||
|
|
e66d012ab7 | ||
|
|
b266119fdf | ||
|
|
1680673d04 | ||
|
|
60970b8214 | ||
|
|
3e40b24737 | ||
|
|
9d7a4c88e0 | ||
|
|
e5e58b1b30 | ||
|
|
10a0974f43 | ||
|
|
be21e43be5 | ||
|
|
bfb0872493 | ||
|
|
e11399aff0 | ||
|
|
9d56d26d45 | ||
|
|
f663bbec12 | ||
|
|
2506d1768b | ||
|
|
4ee94cb845 | ||
|
|
afaca80b37 | ||
|
|
8057835a0d | ||
|
|
e47529ab9c | ||
|
|
939c8e7f68 | ||
|
|
eb576c44f8 | ||
|
|
0522d02d40 | ||
|
|
30bfc80f9b | ||
|
|
c3d2ef1b1f | ||
|
|
a79ddc35a7 | ||
|
|
04542a9f58 | ||
|
|
8761dcb7a8 | ||
|
|
f01231dff8 | ||
|
|
5b18ce0d2e | ||
|
|
4841fdd1ef | ||
|
|
fc75319bb2 | ||
|
|
7c8bdccbdc | ||
|
|
9787fe5d26 | ||
|
|
98d30288f7 | ||
|
|
d74cfd5711 | ||
|
|
cd3a5d50b6 | ||
|
|
772f42134a | ||
|
|
38256d8ff9 | ||
|
|
4e1cf13a50 | ||
|
|
696402c68c | ||
|
|
dc169e425e | ||
|
|
13fc75902a | ||
|
|
454beaa757 | ||
|
|
1a9ffb3814 | ||
|
|
ac16be54c8 | ||
|
|
8a02e3c2f5 | ||
|
|
283326b2f6 | ||
|
|
ca4f50c5ef | ||
|
|
7a3fe830d4 | ||
|
|
ee414231ed | ||
|
|
41ed40f5f9 | ||
|
|
d2cc930975 | ||
|
|
52f5d08d1f | ||
|
|
70bfa2cf39 | ||
|
|
38f779d9d3 | ||
|
|
8cd63cb63e | ||
|
|
cc309a2b34 | ||
|
|
91a4a74641 | ||
|
|
48f38c4bb6 | ||
|
|
6875e9aa88 | ||
|
|
c5ed2cfc28 | ||
|
|
1a0d300786 | ||
|
|
d2447b5c19 | ||
|
|
5900e9e39c | ||
|
|
882003f314 | ||
|
|
db9dc6ae83 | ||
|
|
e0f37f834b | ||
|
|
89b624770c | ||
|
|
79227af89f | ||
|
|
5d12d9071d | ||
|
|
b7add42154 | ||
|
|
dd1bfae4e0 | ||
|
|
bef2d8473b | ||
|
|
609719bde0 | ||
|
|
3a0703f428 | ||
|
|
245981281e | ||
|
|
1d25338119 | ||
|
|
457c7fe5cf | ||
|
|
7998811fa5 | ||
|
|
7044122fc5 | ||
|
|
c63d9e1803 | ||
|
|
d892a9aac5 | ||
|
|
fd8bef68b7 | ||
|
|
986328ff9e | ||
|
|
0ba9d35705 | ||
|
|
cac8e9c821 | ||
|
|
1c7680f689 | ||
|
|
c27d116a66 | ||
|
|
eac711ace6 | ||
|
|
5ae01749bf | ||
|
|
1fd57ede1f | ||
|
|
48fc18e380 | ||
|
|
ea6a94d355 |
17
.github/workflows/release.yml
vendored
17
.github/workflows/release.yml
vendored
@@ -80,11 +80,19 @@ jobs:
|
||||
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \
|
||||
libv4l-dev
|
||||
|
||||
# SDL3 is not available in Ubuntu yet
|
||||
- name: Install SDL3
|
||||
run: |
|
||||
app/deps/sdl.sh linux native shared
|
||||
|
||||
- name: Test
|
||||
run: release/test_client.sh
|
||||
run: |
|
||||
export PKG_CONFIG_PATH="$PWD"/app/deps/work/install/linux-native-shared/lib/pkgconfig
|
||||
export LD_LIBRARY_PATH="$PWD"/app/deps/work/install/linux-native-shared/lib
|
||||
release/test_client.sh
|
||||
|
||||
build-linux-x86_64:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Check architecture
|
||||
run: |
|
||||
@@ -202,8 +210,7 @@ jobs:
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
brew install meson ninja nasm libiconv zlib automake autoconf \
|
||||
libtool
|
||||
brew install meson nasm libiconv zlib automake autoconf libtool
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
@@ -245,7 +252,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: brew install meson ninja nasm libiconv zlib automake
|
||||
run: brew install meson nasm libiconv zlib automake
|
||||
# autoconf and libtool are already installed on macos-13
|
||||
|
||||
- name: Build
|
||||
|
||||
5
FAQ.md
5
FAQ.md
@@ -166,14 +166,13 @@ Rebooting the device is necessary once this option is set.
|
||||
|
||||
### Special characters do not work
|
||||
|
||||
The default text injection method is [limited to ASCII characters][text-input].
|
||||
A trick allows to also inject some [accented characters][accented-characters],
|
||||
The default text injection method is limited to ASCII characters. A trick allows
|
||||
to also inject some [accented characters][accented-characters],
|
||||
but that's all. See [#37].
|
||||
|
||||
To avoid the problem, [change the keyboard mode to simulate a physical
|
||||
keyboard][hid].
|
||||
|
||||
[text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode
|
||||
[accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters
|
||||
[#37]: https://github.com/Genymobile/scrcpy/issues/37
|
||||
[hid]: doc/keyboard.md#physical-keyboard-simulation
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -188,7 +188,7 @@
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright (C) 2018 Genymobile
|
||||
Copyright (C) 2018-2024 Romain Vimont
|
||||
Copyright (C) 2018-2025 Romain Vimont
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
26
README.md
26
README.md
@@ -2,16 +2,16 @@
|
||||
source for the project. Do not download releases from random websites, even if
|
||||
their name contains `scrcpy`.**
|
||||
|
||||
# scrcpy (v3.1)
|
||||
# scrcpy (v3.3.3)
|
||||
|
||||
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
|
||||
|
||||
_pronounced "**scr**een **c**o**py**"_
|
||||
|
||||
This application mirrors Android devices (video and audio) connected via
|
||||
USB or [over TCP/IP](doc/connection.md#tcpip-wireless), and allows to control the
|
||||
device with the keyboard and the mouse of the computer. It does not require any
|
||||
_root_ access. It works on _Linux_, _Windows_ and _macOS_.
|
||||
This application mirrors Android devices (video and audio) connected via USB or
|
||||
[TCP/IP](doc/connection.md#tcpip-wireless) and allows control using the
|
||||
computer's keyboard and mouse. It does not require _root_ access or an app
|
||||
installed on the device. It works on _Linux_, _Windows_, and _macOS_.
|
||||
|
||||

|
||||
|
||||
@@ -58,7 +58,7 @@ Make sure you [enabled USB debugging][enable-adb] on your device(s).
|
||||
On some devices (especially Xiaomi), you might get the following error:
|
||||
|
||||
```
|
||||
java.lang.SecurityException: Injecting input events requires the caller (or the source of the instrumentation, if any) to have the INJECT_EVENTS permission.
|
||||
Injecting input events requires the caller (or the source of the instrumentation, if any) to have the INJECT_EVENTS permission.
|
||||
```
|
||||
|
||||
In that case, you need to enable [an additional option][control] `USB debugging
|
||||
@@ -78,6 +78,16 @@ Note that USB debugging is not required to run scrcpy in [OTG mode](doc/otg.md).
|
||||
- [macOS](doc/macos.md)
|
||||
|
||||
|
||||
## Must-know tips
|
||||
|
||||
- [Reducing resolution](doc/video.md#size) may greatly improve performance
|
||||
(`scrcpy -m1024`)
|
||||
- [_Right-click_](doc/mouse.md#mouse-bindings) triggers `BACK`
|
||||
- [_Middle-click_](doc/mouse.md#mouse-bindings) triggers `HOME`
|
||||
- <kbd>Alt</kbd>+<kbd>f</kbd> toggles [fullscreen](doc/window.md#fullscreen)
|
||||
- There are many other [shortcuts](doc/shortcuts.md)
|
||||
|
||||
|
||||
## Usage examples
|
||||
|
||||
There are a lot of options, [documented](#user-documentation) in separate pages.
|
||||
@@ -197,10 +207,10 @@ work][donate]:
|
||||
|
||||
[donate]: https://blog.rom1v.com/about/#support-my-open-source-work
|
||||
|
||||
## Licence
|
||||
## License
|
||||
|
||||
Copyright (C) 2018 Genymobile
|
||||
Copyright (C) 2018-2024 Romain Vimont
|
||||
Copyright (C) 2018-2025 Romain Vimont
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -23,6 +23,7 @@ _scrcpy() {
|
||||
-d --select-usb
|
||||
--disable-screensaver
|
||||
--display-id=
|
||||
--display-ime-policy=
|
||||
--display-orientation=
|
||||
-e --select-tcpip
|
||||
-f --fullscreen
|
||||
@@ -121,7 +122,7 @@ _scrcpy() {
|
||||
return
|
||||
;;
|
||||
--audio-source)
|
||||
COMPREPLY=($(compgen -W 'output mic playback' -- "$cur"))
|
||||
COMPREPLY=($(compgen -W 'output playback mic mic-unprocessed mic-camcorder mic-voice-recognition mic-voice-communication voice-call voice-call-uplink voice-call-downlink voice-performance' -- "$cur"))
|
||||
return
|
||||
;;
|
||||
--camera-facing)
|
||||
@@ -148,6 +149,10 @@ _scrcpy() {
|
||||
COMPREPLY=($(compgen -W '0 90 180 270 flip0 flip90 flip180 flip270' -- "$cur"))
|
||||
return
|
||||
;;
|
||||
--display-ime-policy)
|
||||
COMPREPLY=($(compgen -W 'local fallback hide' -- "$cur"))
|
||||
return
|
||||
;;
|
||||
--record-orientation)
|
||||
COMPREPLY=($(compgen -W '0 90 180 270' -- "$cur"))
|
||||
return
|
||||
@@ -200,6 +205,7 @@ _scrcpy() {
|
||||
|-p|--port \
|
||||
|--push-target \
|
||||
|--rotation \
|
||||
|--screen-off-timeout \
|
||||
|--tunnel-host \
|
||||
|--tunnel-port \
|
||||
|--v4l2-buffer \
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#compdef -N scrcpy -N scrcpy.exe
|
||||
#compdef scrcpy scrcpy.exe
|
||||
#
|
||||
# name: scrcpy
|
||||
# auth: hltdev [hltdev8642@gmail.com]
|
||||
@@ -11,12 +11,12 @@ arguments=(
|
||||
'--always-on-top[Make scrcpy window always on top \(above other windows\)]'
|
||||
'--angle=[Rotate the video content by a custom angle, in degrees]'
|
||||
'--audio-bit-rate=[Encode the audio at the given bit-rate]'
|
||||
'--audio-buffer=[Configure the audio buffering delay (in milliseconds)]'
|
||||
'--audio-buffer=[Configure the audio buffering delay \(in milliseconds\)]'
|
||||
'--audio-codec=[Select the audio codec]:codec:(opus aac flac raw)'
|
||||
'--audio-codec-options=[Set a list of comma-separated key\:type=value options for the device audio encoder]'
|
||||
'--audio-dup=[Duplicate audio]'
|
||||
'--audio-encoder=[Use a specific MediaCodec audio encoder]'
|
||||
'--audio-source=[Select the audio source]:source:(output mic playback)'
|
||||
'--audio-source=[Select the audio source]:source:(output playback mic mic-unprocessed mic-camcorder mic-voice-recognition mic-voice-communication voice-call voice-call-uplink voice-call-downlink voice-performance)'
|
||||
'--audio-output-buffer=[Configure the size of the SDL audio output buffer (in milliseconds)]'
|
||||
{-b,--video-bit-rate=}'[Encode the video at the given bit-rate]'
|
||||
'--camera-ar=[Select the camera size by its aspect ratio]'
|
||||
@@ -30,14 +30,15 @@ arguments=(
|
||||
{-d,--select-usb}'[Use USB device]'
|
||||
'--disable-screensaver[Disable screensaver while scrcpy is running]'
|
||||
'--display-id=[Specify the display id to mirror]'
|
||||
'--display-ime-policy[Set the policy for selecting where the IME should be displayed]'
|
||||
'--display-orientation=[Set the initial display orientation]:orientation values:(0 90 180 270 flip0 flip90 flip180 flip270)'
|
||||
{-e,--select-tcpip}'[Use TCP/IP device]'
|
||||
{-f,--fullscreen}'[Start in fullscreen]'
|
||||
'--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]'
|
||||
'-G[Use UHID/AOA gamepad (same as --gamepad=uhid or --gamepad=aoa, depending on OTG mode)]'
|
||||
'-G[Use UHID/AOA gamepad \(same as --gamepad=uhid or --gamepad=aoa, depending on OTG mode\)]'
|
||||
'--gamepad=[Set the gamepad input mode]:mode:(disabled uhid aoa)'
|
||||
{-h,--help}'[Print the help]'
|
||||
'-K[Use UHID/AOA keyboard (same as --keyboard=uhid or --keyboard=aoa, depending on OTG mode)]'
|
||||
'-K[Use UHID/AOA keyboard \(same as --keyboard=uhid or --keyboard=aoa, depending on OTG mode\)]'
|
||||
'--keyboard=[Set the keyboard input mode]:mode:(disabled sdk uhid aoa)'
|
||||
'--kill-adb-on-close[Kill adb when scrcpy terminates]'
|
||||
'--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]'
|
||||
@@ -47,7 +48,7 @@ arguments=(
|
||||
'--list-displays[List displays available on the device]'
|
||||
'--list-encoders[List video and audio encoders available on the device]'
|
||||
{-m,--max-size=}'[Limit both the width and height of the video to value]'
|
||||
'-M[Use UHID/AOA mouse (same as --mouse=uhid or --mouse=aoa, depending on OTG mode)]'
|
||||
'-M[Use UHID/AOA mouse \(same as --mouse=uhid or --mouse=aoa, depending on OTG mode\)]'
|
||||
'--max-fps=[Limit the frame rate of screen capture]'
|
||||
'--mouse=[Set the mouse input mode]:mode:(disabled sdk uhid aoa)'
|
||||
'--mouse-bind=[Configure bindings of secondary clicks]'
|
||||
|
||||
@@ -4,10 +4,10 @@ DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
|
||||
cd "$DEPS_DIR"
|
||||
. common
|
||||
|
||||
VERSION=35.0.2
|
||||
VERSION=36.0.0
|
||||
FILENAME=platform-tools_r$VERSION-linux.zip
|
||||
PROJECT_DIR=platform-tools-$VERSION-linux
|
||||
SHA256SUM=acfdcccb123a8718c46c46c059b2f621140194e5ec1ac9d81715be3d6ab6cd0a
|
||||
SHA256SUM=0ead642c943ffe79701fccca8f5f1c69c4ce4f43df2eefee553f6ccb27cbfbe8
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@ DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
|
||||
cd "$DEPS_DIR"
|
||||
. common
|
||||
|
||||
VERSION=35.0.2
|
||||
VERSION=36.0.0
|
||||
FILENAME=platform-tools_r$VERSION-darwin.zip
|
||||
PROJECT_DIR=platform-tools-$VERSION-darwin
|
||||
SHA256SUM=1820078db90bf21628d257ff052528af1c61bb48f754b3555648f5652fa35d78
|
||||
SHA256SUM=d3e9fa1df3345cf728586908426615a60863d2632f73f1ce14f0f1349ef000fd
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@ DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
|
||||
cd "$DEPS_DIR"
|
||||
. common
|
||||
|
||||
VERSION=35.0.2
|
||||
VERSION=36.0.0
|
||||
FILENAME=platform-tools_r$VERSION-win.zip
|
||||
PROJECT_DIR=platform-tools-$VERSION-windows
|
||||
SHA256SUM=2975a3eac0b19182748d64195375ad056986561d994fffbdc64332a516300bb9
|
||||
SHA256SUM=12c2841f354e92a0eb2fd7bf6f0f9bf8538abce7bd6b060ac8349d6f6a61107c
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@ cd "$DEPS_DIR"
|
||||
. common
|
||||
process_args "$@"
|
||||
|
||||
VERSION=7.1
|
||||
VERSION=7.1.1
|
||||
FILENAME=ffmpeg-$VERSION.tar.xz
|
||||
PROJECT_DIR=ffmpeg-$VERSION
|
||||
SHA256SUM=40973D44970DBC83EF302B0609F2E74982BE2D85916DD2EE7472D30678A7ABE6
|
||||
SHA256SUM=733984395e0dbbe5c046abda2dc49a5544e7e0e1e2366bba849222ae9e3a03b1
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@ cd "$DEPS_DIR"
|
||||
. common
|
||||
process_args "$@"
|
||||
|
||||
VERSION=1.0.27
|
||||
VERSION=1.0.29
|
||||
FILENAME=libusb-$VERSION.tar.gz
|
||||
PROJECT_DIR=libusb-$VERSION
|
||||
SHA256SUM=e8f18a7a36ecbb11fb820bd71540350d8f61bcd9db0d2e8c18a6fb80b214a3de
|
||||
SHA256SUM=7c2dd39c0b2589236e48c93247c986ae272e27570942b4163cb00a060fcf1b74
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@ cd "$DEPS_DIR"
|
||||
. common
|
||||
process_args "$@"
|
||||
|
||||
VERSION=2.30.10
|
||||
VERSION=3.2.18
|
||||
FILENAME=SDL-$VERSION.tar.gz
|
||||
PROJECT_DIR=SDL-release-$VERSION
|
||||
SHA256SUM=35a8b9c4f3635d85762b904ac60ca4e0806bff89faeb269caafbe80860d67168
|
||||
SHA256SUM=51539fa13e546bc50c632beed3f34257de2baa38a4c642048de56377903b4265
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
@@ -28,52 +28,55 @@ export CXXFLAGS="$CFLAGS"
|
||||
|
||||
if [[ -d "$DIRNAME" ]]
|
||||
then
|
||||
echo "'$PWD/$HDIRNAME' already exists, not reconfigured"
|
||||
echo "'$PWD/$DIRNAME' already exists, not reconfigured"
|
||||
cd "$DIRNAME"
|
||||
else
|
||||
mkdir "$DIRNAME"
|
||||
cd "$DIRNAME"
|
||||
|
||||
conf=(
|
||||
--prefix="$INSTALL_DIR/$DIRNAME"
|
||||
-DCMAKE_INSTALL_PREFIX="$INSTALL_DIR/$DIRNAME"
|
||||
)
|
||||
|
||||
if [[ "$HOST" == linux ]]
|
||||
then
|
||||
conf+=(
|
||||
--enable-video-wayland
|
||||
--enable-video-x11
|
||||
-DSDL_WAYLAND=ON
|
||||
-DSDL_X11=ON
|
||||
)
|
||||
fi
|
||||
|
||||
if [[ "$LINK_TYPE" == static ]]
|
||||
then
|
||||
conf+=(
|
||||
--enable-static
|
||||
--disable-shared
|
||||
-DBUILD_SHARED_LIBS=OFF
|
||||
)
|
||||
else
|
||||
conf+=(
|
||||
--disable-static
|
||||
--enable-shared
|
||||
-DBUILD_SHARED_LIBS=ON
|
||||
)
|
||||
fi
|
||||
|
||||
if [[ "$BUILD_TYPE" == cross ]]
|
||||
then
|
||||
if [[ "$HOST" = win32 ]]
|
||||
then
|
||||
TOOLCHAIN_FILENAME="cmake-toolchain-mingw64-i686.cmake"
|
||||
elif [[ "$HOST" = win64 ]]
|
||||
then
|
||||
TOOLCHAIN_FILENAME="cmake-toolchain-mingw64-x86_64.cmake"
|
||||
else
|
||||
echo "Unsupported cross-build to host: $HOST" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
conf+=(
|
||||
--host="$HOST_TRIPLET"
|
||||
-DCMAKE_TOOLCHAIN_FILE="$SOURCES_DIR/$PROJECT_DIR/build-scripts/$TOOLCHAIN_FILENAME"
|
||||
)
|
||||
fi
|
||||
|
||||
"$SOURCES_DIR/$PROJECT_DIR"/configure "${conf[@]}"
|
||||
cmake "$SOURCES_DIR/$PROJECT_DIR" "${conf[@]}"
|
||||
fi
|
||||
|
||||
make -j
|
||||
# There is no "make install-strip"
|
||||
make install
|
||||
# Strip manually
|
||||
if [[ "$LINK_TYPE" == shared && "$HOST" == win* ]]
|
||||
then
|
||||
${HOST_TRIPLET}-strip "$INSTALL_DIR/$DIRNAME/bin/SDL2.dll"
|
||||
fi
|
||||
cmake --build .
|
||||
cmake --install .
|
||||
|
||||
@@ -63,6 +63,7 @@ src = [
|
||||
'src/util/thread.c',
|
||||
'src/util/tick.c',
|
||||
'src/util/timeout.c',
|
||||
'src/util/window.c',
|
||||
]
|
||||
|
||||
conf = configuration_data()
|
||||
@@ -117,7 +118,7 @@ dependencies = [
|
||||
dependency('libavcodec', version: '>= 57.37', static: static),
|
||||
dependency('libavutil', static: static),
|
||||
dependency('libswresample', static: static),
|
||||
dependency('sdl2', version: '>= 2.0.5', static: static),
|
||||
dependency('sdl3', version: '>= 3.2.0', static: static),
|
||||
]
|
||||
|
||||
if v4l2_support
|
||||
|
||||
@@ -13,7 +13,7 @@ BEGIN
|
||||
VALUE "LegalCopyright", "Romain Vimont, Genymobile"
|
||||
VALUE "OriginalFilename", "scrcpy.exe"
|
||||
VALUE "ProductName", "scrcpy"
|
||||
VALUE "ProductVersion", "3.1"
|
||||
VALUE "ProductVersion", "3.3.3"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
39
app/scrcpy.1
39
app/scrcpy.1
@@ -67,13 +67,19 @@ The available encoders can be listed by \fB\-\-list\-encoders\fR.
|
||||
|
||||
.TP
|
||||
.BI "\-\-audio\-source " source
|
||||
Select the audio source (output, mic or playback).
|
||||
Select the audio source. Possible values are:
|
||||
|
||||
The "output" source forwards the whole audio output, and disables playback on the device.
|
||||
|
||||
The "playback" source captures the audio playback (Android apps can opt-out, so the whole output is not necessarily captured).
|
||||
|
||||
The "mic" source captures the microphone.
|
||||
- "output": forwards the whole audio output, and disables playback on the device.
|
||||
- "playback": captures the audio playback (Android apps can opt-out, so the whole output is not necessarily captured).
|
||||
- "mic": captures the microphone.
|
||||
- "mic-unprocessed": captures the microphone unprocessed (raw) sound.
|
||||
- "mic-camcorder": captures the microphone tuned for video recording, with the same orientation as the camera if available.
|
||||
- "mic-voice-recognition": captures the microphone tuned for voice recognition.
|
||||
- "mic-voice-communication": captures the microphone tuned for voice communications (it will for instance take advantage of echo cancellation or automatic gain control if available).
|
||||
- "voice-call": captures voice call.
|
||||
- "voice-call-uplink": captures voice call uplink only.
|
||||
- "voice-call-downlink": captures voice call downlink only.
|
||||
- "voice-performance": captures audio meant to be processed for live performance (karaoke), includes both the microphone and the device playback.
|
||||
|
||||
Default is output.
|
||||
|
||||
@@ -161,6 +167,19 @@ The available display ids can be listed by \fB\-\-list\-displays\fR.
|
||||
|
||||
Default is 0.
|
||||
|
||||
.TP
|
||||
.BI "\-\-display\-ime\-policy " value
|
||||
Set the policy for selecting where the IME should be displayed.
|
||||
|
||||
Possible values are "local", "fallback" and "hide":
|
||||
|
||||
- "local" means that the IME should appear on the local display.
|
||||
- "fallback" means that the IME should appear on a fallback display (the default display).
|
||||
- "hide" means that the IME should be hidden.
|
||||
|
||||
By default, the IME policy is left unchanged.
|
||||
|
||||
|
||||
.TP
|
||||
.BI "\-\-display\-orientation " value
|
||||
Set the initial display orientation.
|
||||
@@ -389,7 +408,7 @@ Disable video playback on the computer.
|
||||
|
||||
.TP
|
||||
.B \-\-no\-window
|
||||
Disable scrcpy window. Implies --no-video-playback and --no-control.
|
||||
Disable scrcpy window. Implies --no-video-playback.
|
||||
|
||||
.TP
|
||||
.BI "\-\-orientation " value
|
||||
@@ -491,6 +510,10 @@ The device serial number. Mandatory only if several devices are connected to adb
|
||||
.B \-S, \-\-turn\-screen\-off
|
||||
Turn the device screen off immediately.
|
||||
|
||||
.TP
|
||||
.B "\-\-screen\-off\-timeout " seconds
|
||||
Set the screen off timeout while scrcpy is running (restore the initial value on exit).
|
||||
|
||||
.TP
|
||||
.BI "\-\-shortcut\-mod " key\fR[+...]][,...]
|
||||
Specify the modifiers to use for scrcpy shortcuts. Possible keys are "lctrl", "rctrl", "lalt", "ralt", "lsuper" and "rsuper".
|
||||
@@ -829,7 +852,7 @@ Report bugs to <https://github.com/Genymobile/scrcpy/issues>.
|
||||
.SH COPYRIGHT
|
||||
Copyright \(co 2018 Genymobile <https://www.genymobile.com>
|
||||
|
||||
Copyright \(co 2018\-2024 Romain Vimont <rom@rom1v.com>
|
||||
Copyright \(co 2018\-2025 Romain Vimont <rom@rom1v.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0.
|
||||
|
||||
|
||||
@@ -103,14 +103,14 @@ argv_to_string(const char *const *argv, char *buf, size_t bufsize) {
|
||||
|
||||
static void
|
||||
show_adb_installation_msg(void) {
|
||||
#ifndef __WINDOWS__
|
||||
#ifndef _WIN32
|
||||
static const struct {
|
||||
const char *binary;
|
||||
const char *command;
|
||||
} pkg_managers[] = {
|
||||
{"apt", "apt install adb"},
|
||||
{"apt-get", "apt-get install adb"},
|
||||
{"brew", "brew cask install android-platform-tools"},
|
||||
{"brew", "brew install --cask android-platform-tools"},
|
||||
{"dnf", "dnf install android-tools"},
|
||||
{"emerge", "emerge dev-util/android-tools"},
|
||||
{"pacman", "pacman -S android-tools"},
|
||||
@@ -331,7 +331,7 @@ sc_adb_reverse_remove(struct sc_intr *intr, const char *serial,
|
||||
bool
|
||||
sc_adb_push(struct sc_intr *intr, const char *serial, const char *local,
|
||||
const char *remote, unsigned flags) {
|
||||
#ifdef __WINDOWS__
|
||||
#ifdef _WIN32
|
||||
// Windows will parse the string, so the paths must be quoted
|
||||
// (see sys/win/command.c)
|
||||
local = sc_str_quote(local);
|
||||
@@ -351,7 +351,7 @@ sc_adb_push(struct sc_intr *intr, const char *serial, const char *local,
|
||||
|
||||
sc_pid pid = sc_adb_execute(argv, flags);
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
#ifdef _WIN32
|
||||
free((void *) remote);
|
||||
free((void *) local);
|
||||
#endif
|
||||
@@ -362,7 +362,7 @@ sc_adb_push(struct sc_intr *intr, const char *serial, const char *local,
|
||||
bool
|
||||
sc_adb_install(struct sc_intr *intr, const char *serial, const char *local,
|
||||
unsigned flags) {
|
||||
#ifdef __WINDOWS__
|
||||
#ifdef _WIN32
|
||||
// Windows will parse the string, so the local name must be quoted
|
||||
// (see sys/win/command.c)
|
||||
local = sc_str_quote(local);
|
||||
@@ -377,7 +377,7 @@ sc_adb_install(struct sc_intr *intr, const char *serial, const char *local,
|
||||
|
||||
sc_pid pid = sc_adb_execute(argv, flags);
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
#ifdef _WIN32
|
||||
free((void *) local);
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1,23 +1,40 @@
|
||||
#include "audio_player.h"
|
||||
|
||||
#include "util/log.h"
|
||||
#include "SDL3/SDL_hints.h"
|
||||
|
||||
/** Downcast frame_sink to sc_audio_player */
|
||||
#define DOWNCAST(SINK) container_of(SINK, struct sc_audio_player, frame_sink)
|
||||
|
||||
#define SC_SDL_SAMPLE_FMT AUDIO_F32
|
||||
#define SC_SDL_SAMPLE_FMT SDL_AUDIO_F32LE
|
||||
|
||||
static void SDLCALL
|
||||
sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) {
|
||||
sc_audio_player_stream_callback(void *userdata, SDL_AudioStream *stream,
|
||||
int additional_amount, int total_amount) {
|
||||
(void) total_amount;
|
||||
|
||||
struct sc_audio_player *ap = userdata;
|
||||
|
||||
assert(len_int > 0);
|
||||
size_t len = len_int;
|
||||
|
||||
size_t len = additional_amount;
|
||||
assert(len % ap->audioreg.sample_size == 0);
|
||||
uint32_t out_samples = len / ap->audioreg.sample_size;
|
||||
|
||||
sc_audio_regulator_pull(&ap->audioreg, stream, out_samples);
|
||||
// The requested amount may exceed the internal aout_buffer size.
|
||||
// In this (unlikely) case, send the data to the stream in multiple chunks.
|
||||
while (len) {
|
||||
size_t chunk_size = MIN(ap->aout_buffer_size, len);
|
||||
uint32_t out_samples = chunk_size / ap->audioreg.sample_size;
|
||||
sc_audio_regulator_pull(&ap->audioreg, ap->aout_buffer,
|
||||
out_samples);
|
||||
|
||||
assert(chunk_size <= len);
|
||||
len -= chunk_size;
|
||||
bool ok =
|
||||
SDL_PutAudioStreamData(stream, ap->aout_buffer, chunk_size);
|
||||
if (!ok) {
|
||||
LOGW("Audio stream error: %s", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
@@ -30,7 +47,10 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
|
||||
|
||||
static bool
|
||||
sc_audio_player_frame_sink_open(struct sc_frame_sink *sink,
|
||||
const AVCodecContext *ctx) {
|
||||
const AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session) {
|
||||
(void) session;
|
||||
|
||||
struct sc_audio_player *ap = DOWNCAST(sink);
|
||||
|
||||
#ifdef SCRCPY_LAVU_HAS_CHLAYOUT
|
||||
@@ -61,23 +81,45 @@ sc_audio_player_frame_sink_open(struct sc_frame_sink *sink,
|
||||
/ SC_TICK_FREQ;
|
||||
assert(aout_samples <= 0xFFFF);
|
||||
|
||||
SDL_AudioSpec desired = {
|
||||
.freq = ctx->sample_rate,
|
||||
.format = SC_SDL_SAMPLE_FMT,
|
||||
.channels = nb_channels,
|
||||
.samples = aout_samples,
|
||||
.callback = sc_audio_player_sdl_callback,
|
||||
.userdata = ap,
|
||||
};
|
||||
SDL_AudioSpec obtained;
|
||||
|
||||
ap->device = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, 0);
|
||||
if (!ap->device) {
|
||||
LOGE("Could not open audio device: %s", SDL_GetError());
|
||||
char str[5 + 1]; // max 65535
|
||||
int r = snprintf(str, sizeof(str), "%" PRIu16, (uint16_t) aout_samples);
|
||||
assert(r >= 0 && (size_t) r < sizeof(str));
|
||||
(void) r;
|
||||
if (!SDL_SetHint(SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES, str)) {
|
||||
LOGE("Could not set audio output buffer");
|
||||
sc_audio_regulator_destroy(&ap->audioreg);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make the buffer at least 1024 samples long (the hint is not always
|
||||
// honored)
|
||||
uint64_t aout_buffer_samples = MAX(1024, aout_samples);
|
||||
ap->aout_buffer_size = aout_buffer_samples * sample_size;
|
||||
ap->aout_buffer = malloc(ap->aout_buffer_size);
|
||||
if (!ap->aout_buffer) {
|
||||
sc_audio_regulator_destroy(&ap->audioreg);
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_AudioSpec spec = {
|
||||
.freq = ctx->sample_rate,
|
||||
.format = SC_SDL_SAMPLE_FMT,
|
||||
.channels = nb_channels,
|
||||
};
|
||||
|
||||
ap->stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK,
|
||||
&spec,
|
||||
sc_audio_player_stream_callback, ap);
|
||||
if (!ap->stream) {
|
||||
LOGE("Could not open audio device: %s", SDL_GetError());
|
||||
free(ap->aout_buffer);
|
||||
sc_audio_regulator_destroy(&ap->audioreg);
|
||||
return false;
|
||||
}
|
||||
|
||||
ap->device = SDL_GetAudioStreamDevice(ap->stream);
|
||||
assert(ap->device);
|
||||
|
||||
// The thread calling open() is the thread calling push(), which fills the
|
||||
// audio buffer consumed by the SDL audio thread.
|
||||
ok = sc_thread_set_priority(SC_THREAD_PRIORITY_TIME_CRITICAL);
|
||||
@@ -86,7 +128,14 @@ sc_audio_player_frame_sink_open(struct sc_frame_sink *sink,
|
||||
(void) ok; // We don't care if it worked, at least we tried
|
||||
}
|
||||
|
||||
SDL_PauseAudioDevice(ap->device, 0);
|
||||
ok = SDL_ResumeAudioDevice(ap->device);
|
||||
if (!ok) {
|
||||
LOGE("Could not resume audio device: %s", SDL_GetError());
|
||||
SDL_DestroyAudioStream(ap->stream);
|
||||
free(ap->aout_buffer);
|
||||
sc_audio_regulator_destroy(&ap->audioreg);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -95,11 +144,16 @@ static void
|
||||
sc_audio_player_frame_sink_close(struct sc_frame_sink *sink) {
|
||||
struct sc_audio_player *ap = DOWNCAST(sink);
|
||||
|
||||
assert(ap->stream);
|
||||
assert(ap->device);
|
||||
SDL_PauseAudioDevice(ap->device, 1);
|
||||
SDL_CloseAudioDevice(ap->device);
|
||||
SDL_PauseAudioDevice(ap->device);
|
||||
|
||||
// ap->device is owned by ap->stream
|
||||
SDL_DestroyAudioStream(ap->stream);
|
||||
|
||||
sc_audio_regulator_destroy(&ap->audioreg);
|
||||
|
||||
free(ap->aout_buffer);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <SDL2/SDL_audio.h>
|
||||
#include <SDL3/SDL_audio.h>
|
||||
|
||||
#include "audio_regulator.h"
|
||||
#include "trait/frame_sink.h"
|
||||
@@ -22,7 +22,11 @@ struct sc_audio_player {
|
||||
// SDL audio output buffer size
|
||||
sc_tick output_buffer_duration;
|
||||
|
||||
SDL_AudioDeviceID device;
|
||||
uint8_t *aout_buffer;
|
||||
size_t aout_buffer_size;
|
||||
|
||||
SDL_AudioStream *stream;
|
||||
SDL_AudioDeviceID device; // owned by the audio stream
|
||||
struct sc_audio_regulator audioreg;
|
||||
};
|
||||
|
||||
|
||||
@@ -76,8 +76,10 @@ sc_audio_regulator_pull(struct sc_audio_regulator *ar, uint8_t *out,
|
||||
// Wait until the buffer is filled up to at least target_buffering
|
||||
// before playing
|
||||
if (buffered_samples < ar->target_buffering) {
|
||||
LOGV("[Audio] Inserting initial buffering silence: %" PRIu32
|
||||
#ifdef SC_AUDIO_REGULATOR_DEBUG
|
||||
LOGD("[Audio] Inserting initial buffering silence: %" PRIu32
|
||||
" samples", out_samples);
|
||||
#endif
|
||||
// Delay playback starting to reach the target buffering. Fill the
|
||||
// whole buffer with silence (len is small compared to the
|
||||
// arbitrary margin value).
|
||||
@@ -98,8 +100,10 @@ sc_audio_regulator_pull(struct sc_audio_regulator *ar, uint8_t *out,
|
||||
// dropped to keep the latency minimal. However, this would cause very
|
||||
// audible glitches, so let the clock compensation restore the target
|
||||
// latency.
|
||||
#ifdef SC_AUDIO_REGULATOR_DEBUG
|
||||
LOGD("[Audio] Buffer underflow, inserting silence: %" PRIu32 " samples",
|
||||
silence);
|
||||
#endif
|
||||
memset(out + TO_BYTES(read), 0, TO_BYTES(silence));
|
||||
|
||||
bool received = atomic_load_explicit(&ar->received,
|
||||
@@ -137,6 +141,36 @@ bool
|
||||
sc_audio_regulator_push(struct sc_audio_regulator *ar, const AVFrame *frame) {
|
||||
SwrContext *swr_ctx = ar->swr_ctx;
|
||||
|
||||
uint32_t input_samples = frame->nb_samples;
|
||||
|
||||
assert(frame->pts >= 0);
|
||||
int64_t pts = frame->pts;
|
||||
if (ar->next_expected_pts && pts - ar->next_expected_pts > 100000) {
|
||||
LOGV("[Audio] Discontinuity detected: %" PRIi64 "µs",
|
||||
pts - ar->next_expected_pts);
|
||||
// More than 100ms: consider it as a discontinuity
|
||||
// (typically because silence packets were not captured)
|
||||
uint32_t can_read = sc_audiobuf_can_read(&ar->buf);
|
||||
if (input_samples + can_read < ar->target_buffering) {
|
||||
// Adjust buffering to the target value directly
|
||||
uint32_t silence = ar->target_buffering - can_read - input_samples;
|
||||
sc_audiobuf_write_silence(&ar->buf, silence);
|
||||
}
|
||||
|
||||
// Reset state
|
||||
ar->avg_buffering.avg = ar->target_buffering;
|
||||
int ret = swr_set_compensation(swr_ctx, 0, 0);
|
||||
(void) ret;
|
||||
assert(!ret); // disabling compensation should never fail
|
||||
ar->compensation_active = false;
|
||||
ar->samples_since_resync = 0;
|
||||
atomic_store_explicit(&ar->underflow, 0, memory_order_relaxed);
|
||||
}
|
||||
|
||||
int64_t packet_duration = input_samples * INT64_C(1000000)
|
||||
/ ar->sample_rate;
|
||||
ar->next_expected_pts = pts + packet_duration;
|
||||
|
||||
int64_t swr_delay = swr_get_delay(swr_ctx, ar->sample_rate);
|
||||
// No need to av_rescale_rnd(), input and output sample rates are the same.
|
||||
// Add more space (256) for clock compensation.
|
||||
@@ -209,6 +243,7 @@ sc_audio_regulator_push(struct sc_audio_regulator *ar, const AVFrame *frame) {
|
||||
if (played) {
|
||||
underflow = atomic_exchange_explicit(&ar->underflow, 0,
|
||||
memory_order_relaxed);
|
||||
ar->underflow_report += underflow;
|
||||
|
||||
max_buffered_samples = ar->target_buffering * 11 / 10
|
||||
+ 60 * ar->sample_rate / 1000 /* 60 ms */;
|
||||
@@ -255,7 +290,7 @@ sc_audio_regulator_push(struct sc_audio_regulator *ar, const AVFrame *frame) {
|
||||
}
|
||||
|
||||
// Number of samples added (or removed, if negative) for compensation
|
||||
int32_t instant_compensation = (int32_t) written - frame->nb_samples;
|
||||
int32_t instant_compensation = (int32_t) written - input_samples;
|
||||
// Inserting silence instantly increases buffering
|
||||
int32_t inserted_silence = (int32_t) underflow;
|
||||
// Dropping input samples instantly decreases buffering
|
||||
@@ -311,7 +346,9 @@ sc_audio_regulator_push(struct sc_audio_regulator *ar, const AVFrame *frame) {
|
||||
int abs_max_diff = distance / 50;
|
||||
diff = CLAMP(diff, -abs_max_diff, abs_max_diff);
|
||||
LOGV("[Audio] Buffering: target=%" PRIu32 " avg=%f cur=%" PRIu32
|
||||
" compensation=%d", ar->target_buffering, avg, can_read, diff);
|
||||
" compensation=%d (underflow=%" PRIu32 ")",
|
||||
ar->target_buffering, avg, can_read, diff, ar->underflow_report);
|
||||
ar->underflow_report = 0;
|
||||
|
||||
int ret = swr_set_compensation(swr_ctx, diff, distance);
|
||||
if (ret < 0) {
|
||||
@@ -394,7 +431,9 @@ sc_audio_regulator_init(struct sc_audio_regulator *ar, size_t sample_size,
|
||||
atomic_init(&ar->played, false);
|
||||
atomic_init(&ar->received, false);
|
||||
atomic_init(&ar->underflow, 0);
|
||||
ar->underflow_report = 0;
|
||||
ar->compensation_active = false;
|
||||
ar->next_expected_pts = 0;
|
||||
|
||||
return true;
|
||||
|
||||
|
||||
@@ -46,6 +46,9 @@ struct sc_audio_regulator {
|
||||
// Number of silence samples inserted since the last received packet
|
||||
atomic_uint_least32_t underflow;
|
||||
|
||||
// Number of silence samples inserted since the last log
|
||||
uint32_t underflow_report;
|
||||
|
||||
// Non-zero compensation applied (only used by the receiver thread)
|
||||
bool compensation_active;
|
||||
|
||||
@@ -54,6 +57,9 @@ struct sc_audio_regulator {
|
||||
|
||||
// Set to true the first time samples are pulled by the player
|
||||
atomic_bool played;
|
||||
|
||||
// PTS of the next expected packet (useful to detect discontinuities)
|
||||
int64_t next_expected_pts;
|
||||
};
|
||||
|
||||
bool
|
||||
|
||||
135
app/src/cli.c
135
app/src/cli.c
@@ -113,6 +113,7 @@ enum {
|
||||
OPT_ANGLE,
|
||||
OPT_NO_VD_SYSTEM_DECORATIONS,
|
||||
OPT_NO_VD_DESTROY_CONTENT,
|
||||
OPT_DISPLAY_IME_POLICY,
|
||||
};
|
||||
|
||||
struct sc_option {
|
||||
@@ -216,13 +217,31 @@ static const struct sc_option options[] = {
|
||||
.longopt_id = OPT_AUDIO_SOURCE,
|
||||
.longopt = "audio-source",
|
||||
.argdesc = "source",
|
||||
.text = "Select the audio source (output, mic or playback).\n"
|
||||
"The \"output\" source forwards the whole audio output, and "
|
||||
"disables playback on the device.\n"
|
||||
"The \"playback\" source captures the audio playback (Android "
|
||||
"apps can opt-out, so the whole output is not necessarily "
|
||||
.text = "Select the audio source. Possible values are:\n"
|
||||
" - \"output\": forwards the whole audio output, and disables "
|
||||
"playback on the device.\n"
|
||||
" - \"playback\": captures the audio playback (Android apps "
|
||||
"can opt-out, so the whole output is not necessarily "
|
||||
"captured).\n"
|
||||
"The \"mic\" source captures the microphone.\n"
|
||||
" - \"mic\": captures the microphone.\n"
|
||||
" - \"mic-unprocessed\": captures the microphone unprocessed "
|
||||
"(raw) sound.\n"
|
||||
" - \"mic-camcorder\": captures the microphone tuned for video "
|
||||
"recording, with the same orientation as the camera if "
|
||||
"available.\n"
|
||||
" - \"mic-voice-recognition\": captures the microphone tuned "
|
||||
"for voice recognition.\n"
|
||||
" - \"mic-voice-communication\": captures the microphone tuned "
|
||||
"for voice communications (it will for instance take advantage "
|
||||
"of echo cancellation or automatic gain control if "
|
||||
"available).\n"
|
||||
" - \"voice-call\": captures voice call.\n"
|
||||
" - \"voice-call-uplink\": captures voice call uplink only.\n"
|
||||
" - \"voice-call-downlink\": captures voice call downlink "
|
||||
"only.\n"
|
||||
" - \"voice-performance\": captures audio meant to be "
|
||||
"processed for live performance (karaoke), includes both the "
|
||||
"microphone and the device playback.\n"
|
||||
"Default is output.",
|
||||
},
|
||||
{
|
||||
@@ -366,6 +385,19 @@ static const struct sc_option options[] = {
|
||||
" scrcpy --list-displays\n"
|
||||
"Default is 0.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_DISPLAY_IME_POLICY,
|
||||
.longopt = "display-ime-policy",
|
||||
.argdesc = "value",
|
||||
.text = "Set the policy for selecting where the IME should be "
|
||||
"displayed.\n"
|
||||
"Possible values are \"local\", \"fallback\" and \"hide\".\n"
|
||||
"\"local\" means that the IME should appear on the local "
|
||||
"display.\n"
|
||||
"\"fallback\" means that the IME should appear on a fallback "
|
||||
"display (the default display).\n"
|
||||
"\"hide\" means that the IME should be hidden.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_DISPLAY_ORIENTATION,
|
||||
.longopt = "display-orientation",
|
||||
@@ -689,8 +721,7 @@ static const struct sc_option options[] = {
|
||||
{
|
||||
.longopt_id = OPT_NO_WINDOW,
|
||||
.longopt = "no-window",
|
||||
.text = "Disable scrcpy window. Implies --no-video-playback and "
|
||||
"--no-control.",
|
||||
.text = "Disable scrcpy window. Implies --no-video-playback.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_ORIENTATION,
|
||||
@@ -1615,6 +1646,25 @@ parse_audio_output_buffer(const char *s, sc_tick *tick) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_display_ime_policy(const char *s, enum sc_display_ime_policy *policy) {
|
||||
if (!strcmp(s, "local")) {
|
||||
*policy = SC_DISPLAY_IME_POLICY_LOCAL;
|
||||
return true;
|
||||
}
|
||||
if (!strcmp(s, "fallback")) {
|
||||
*policy = SC_DISPLAY_IME_POLICY_FALLBACK;
|
||||
return true;
|
||||
}
|
||||
if (!strcmp(s, "hide")) {
|
||||
*policy = SC_DISPLAY_IME_POLICY_HIDE;
|
||||
return true;
|
||||
}
|
||||
LOGE("Unsupported display IME policy: %s (expected local, fallback or "
|
||||
"hide)", s);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_orientation(const char *s, enum sc_orientation *orientation) {
|
||||
if (!strcmp(s, "0")) {
|
||||
@@ -2004,8 +2054,50 @@ parse_audio_source(const char *optarg, enum sc_audio_source *source) {
|
||||
return true;
|
||||
}
|
||||
|
||||
LOGE("Unsupported audio source: %s (expected output, mic or playback)",
|
||||
optarg);
|
||||
if (!strcmp(optarg, "mic-unprocessed")) {
|
||||
*source = SC_AUDIO_SOURCE_MIC_UNPROCESSED;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!strcmp(optarg, "mic-camcorder")) {
|
||||
*source = SC_AUDIO_SOURCE_MIC_CAMCORDER;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!strcmp(optarg, "mic-voice-recognition")) {
|
||||
*source = SC_AUDIO_SOURCE_MIC_VOICE_RECOGNITION;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!strcmp(optarg, "mic-voice-communication")) {
|
||||
*source = SC_AUDIO_SOURCE_MIC_VOICE_COMMUNICATION;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!strcmp(optarg, "voice-call")) {
|
||||
*source = SC_AUDIO_SOURCE_VOICE_CALL;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!strcmp(optarg, "voice-call-uplink")) {
|
||||
*source = SC_AUDIO_SOURCE_VOICE_CALL_UPLINK;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!strcmp(optarg, "voice-call-downlink")) {
|
||||
*source = SC_AUDIO_SOURCE_VOICE_CALL_DOWNLINK;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!strcmp(optarg, "voice-performance")) {
|
||||
*source = SC_AUDIO_SOURCE_VOICE_PERFORMANCE;
|
||||
return true;
|
||||
}
|
||||
|
||||
LOGE("Unsupported audio source: %s (expected output, mic, playback, "
|
||||
"mic-unprocessed, mic-camcorder, mic-voice-recognition, "
|
||||
"mic-voice-communication, voice-call, voice-call-uplink, "
|
||||
"voice-call-downlink, voice-performance)", optarg);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2723,6 +2815,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
case OPT_NO_VD_SYSTEM_DECORATIONS:
|
||||
opts->vd_system_decorations = false;
|
||||
break;
|
||||
case OPT_DISPLAY_IME_POLICY:
|
||||
if (!parse_display_ime_policy(optarg,
|
||||
&opts->display_ime_policy)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// getopt prints the error message on stderr
|
||||
return false;
|
||||
@@ -2761,9 +2859,10 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
#endif
|
||||
|
||||
if (!opts->window) {
|
||||
// Without window, there cannot be any video playback or control
|
||||
// Without window, there cannot be any video playback
|
||||
opts->video_playback = false;
|
||||
opts->control = false;
|
||||
// Controls are still possible, allowing for options like
|
||||
// --turn-screen-off
|
||||
}
|
||||
|
||||
if (!opts->video) {
|
||||
@@ -2978,6 +3077,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opts->display_ime_policy != SC_DISPLAY_IME_POLICY_UNDEFINED) {
|
||||
LOGE("--display-ime-policy is only available with "
|
||||
"--video-source=display");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opts->camera_id && opts->camera_facing != SC_CAMERA_FACING_ANY) {
|
||||
LOGE("Cannot specify both --camera-id and --camera-facing");
|
||||
return false;
|
||||
@@ -3019,6 +3124,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opts->display_ime_policy != SC_DISPLAY_IME_POLICY_UNDEFINED
|
||||
&& opts->display_id == 0 && !opts->new_display) {
|
||||
LOGE("--display-ime-policy is only supported on a secondary display");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opts->audio && opts->audio_source == SC_AUDIO_SOURCE_AUTO) {
|
||||
// Select the audio source according to the video source
|
||||
if (opts->video_source == SC_VIDEO_SOURCE_DISPLAY) {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <libavcodec/version.h>
|
||||
#include <libavformat/version.h>
|
||||
#include <libavutil/version.h>
|
||||
#include <SDL2/SDL_version.h>
|
||||
#include <SDL3/SDL_version.h>
|
||||
|
||||
#ifndef _WIN32
|
||||
# define PRIu64_ PRIu64
|
||||
@@ -61,20 +61,6 @@
|
||||
# define SCRCPY_LAVC_HAS_CODECPAR_CODEC_SIDEDATA
|
||||
#endif
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 6)
|
||||
// <https://github.com/libsdl-org/SDL/commit/d7a318de563125e5bb465b1000d6bc9576fbc6fc>
|
||||
# define SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS
|
||||
#endif
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 8)
|
||||
// <https://hg.libsdl.org/SDL/rev/dfde5d3f9781>
|
||||
# define SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
|
||||
#endif
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 16)
|
||||
# define SCRCPY_SDL_HAS_THREAD_PRIORITY_TIME_CRITICAL
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_STRDUP
|
||||
char *strdup(const char *s);
|
||||
#endif
|
||||
|
||||
@@ -127,10 +127,14 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
|
||||
return 32;
|
||||
case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
|
||||
write_position(&buf[1], &msg->inject_scroll_event.position);
|
||||
int16_t hscroll =
|
||||
sc_float_to_i16fp(msg->inject_scroll_event.hscroll);
|
||||
int16_t vscroll =
|
||||
sc_float_to_i16fp(msg->inject_scroll_event.vscroll);
|
||||
// Accept values in the range [-16, 16].
|
||||
// Normalize to [-1, 1] in order to use sc_float_to_i16fp().
|
||||
float hscroll_norm = msg->inject_scroll_event.hscroll / 16;
|
||||
hscroll_norm = CLAMP(hscroll_norm, -1, 1);
|
||||
float vscroll_norm = msg->inject_scroll_event.vscroll / 16;
|
||||
vscroll_norm = CLAMP(vscroll_norm, -1, 1);
|
||||
int16_t hscroll = sc_float_to_i16fp(hscroll_norm);
|
||||
int16_t vscroll = sc_float_to_i16fp(vscroll_norm);
|
||||
sc_write16be(&buf[13], (uint16_t) hscroll);
|
||||
sc_write16be(&buf[15], (uint16_t) vscroll);
|
||||
sc_write32be(&buf[17], msg->inject_scroll_event.buttons);
|
||||
|
||||
@@ -10,20 +10,30 @@
|
||||
#define DOWNCAST(SINK) container_of(SINK, struct sc_decoder, packet_sink)
|
||||
|
||||
static bool
|
||||
sc_decoder_open(struct sc_decoder *decoder, AVCodecContext *ctx) {
|
||||
sc_decoder_open(struct sc_decoder *decoder, AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session) {
|
||||
decoder->frame = av_frame_alloc();
|
||||
if (!decoder->frame) {
|
||||
LOG_OOM();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sc_frame_source_sinks_open(&decoder->frame_source, ctx)) {
|
||||
if (!sc_frame_source_sinks_open(&decoder->frame_source, ctx, session)) {
|
||||
av_frame_free(&decoder->frame);
|
||||
return false;
|
||||
}
|
||||
|
||||
decoder->ctx = ctx;
|
||||
|
||||
// A video stream must have a session
|
||||
assert(session || ctx->codec_type != AVMEDIA_TYPE_VIDEO);
|
||||
|
||||
if (session) {
|
||||
decoder->session = *session;
|
||||
}
|
||||
|
||||
memset(&decoder->frame_size, 0, sizeof(decoder->frame_size));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -61,6 +71,32 @@ sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) {
|
||||
}
|
||||
|
||||
// a frame was received
|
||||
|
||||
if (decoder->ctx->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||
assert(decoder->frame->width >= 0);
|
||||
assert(decoder->frame->height >= 0);
|
||||
struct sc_size frame_size = {
|
||||
.width = decoder->frame->width,
|
||||
.height = decoder->frame->height,
|
||||
};
|
||||
if (decoder->frame_size.width != frame_size.width
|
||||
|| decoder->frame_size.height != frame_size.height) {
|
||||
// The frame size has changed, check if it matches the session
|
||||
uint32_t sw = decoder->session.video.width;
|
||||
uint32_t sh = decoder->session.video.height;
|
||||
if (frame_size.width != sw || frame_size.height != sh) {
|
||||
LOGW("Unexpected video size: %" PRIu32 "x%" PRIu32
|
||||
" (expected %" PRIu32 "x%" PRIu32 ")",
|
||||
frame_size.width, frame_size.height, sw, sh);
|
||||
|
||||
LOGW("The encoder did not respect the requested size, "
|
||||
"please retry with a lower resolution (-m/--max-size)");
|
||||
}
|
||||
}
|
||||
|
||||
decoder->frame_size = frame_size;
|
||||
}
|
||||
|
||||
bool ok = sc_frame_source_sinks_push(&decoder->frame_source,
|
||||
decoder->frame);
|
||||
av_frame_unref(decoder->frame);
|
||||
@@ -74,9 +110,17 @@ sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) {
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_decoder_packet_sink_open(struct sc_packet_sink *sink, AVCodecContext *ctx) {
|
||||
sc_decoder_push_session(struct sc_decoder *decoder,
|
||||
const struct sc_stream_session *session) {
|
||||
decoder->session = *session;
|
||||
return sc_frame_source_sinks_push_session(&decoder->frame_source, session);
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_decoder_packet_sink_open(struct sc_packet_sink *sink, AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session) {
|
||||
struct sc_decoder *decoder = DOWNCAST(sink);
|
||||
return sc_decoder_open(decoder, ctx);
|
||||
return sc_decoder_open(decoder, ctx, session);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -92,6 +136,14 @@ sc_decoder_packet_sink_push(struct sc_packet_sink *sink,
|
||||
return sc_decoder_push(decoder, packet);
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_decoder_packet_sink_push_session(struct sc_packet_sink *sink,
|
||||
const struct sc_stream_session *session) {
|
||||
|
||||
struct sc_decoder *decoder = DOWNCAST(sink);
|
||||
return sc_decoder_push_session(decoder, session);
|
||||
}
|
||||
|
||||
void
|
||||
sc_decoder_init(struct sc_decoder *decoder, const char *name) {
|
||||
decoder->name = name; // statically allocated
|
||||
@@ -101,6 +153,7 @@ sc_decoder_init(struct sc_decoder *decoder, const char *name) {
|
||||
.open = sc_decoder_packet_sink_open,
|
||||
.close = sc_decoder_packet_sink_close,
|
||||
.push = sc_decoder_packet_sink_push,
|
||||
.push_session = sc_decoder_packet_sink_push_session,
|
||||
};
|
||||
|
||||
decoder->packet_sink.ops = &ops;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <libavcodec/avcodec.h>
|
||||
|
||||
#include "coords.h"
|
||||
#include "trait/frame_source.h"
|
||||
#include "trait/packet_sink.h"
|
||||
|
||||
@@ -16,6 +17,9 @@ struct sc_decoder {
|
||||
|
||||
AVCodecContext *ctx;
|
||||
AVFrame *frame;
|
||||
|
||||
struct sc_stream_session session; // only initialized for video stream
|
||||
struct sc_size frame_size;
|
||||
};
|
||||
|
||||
// The name must be statically allocated (e.g. a string literal)
|
||||
|
||||
@@ -10,16 +10,18 @@
|
||||
#define DOWNCAST(SINK) container_of(SINK, struct sc_delay_buffer, frame_sink)
|
||||
|
||||
static bool
|
||||
sc_delayed_frame_init(struct sc_delayed_frame *dframe, const AVFrame *frame) {
|
||||
dframe->frame = av_frame_alloc();
|
||||
if (!dframe->frame) {
|
||||
sc_delayed_packet_init_frame(struct sc_delayed_packet *dpacket,
|
||||
const AVFrame *frame) {
|
||||
dpacket->type = SC_DELAYED_PACKET_TYPE_FRAME;
|
||||
dpacket->frame = av_frame_alloc();
|
||||
if (!dpacket->frame) {
|
||||
LOG_OOM();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (av_frame_ref(dframe->frame, frame)) {
|
||||
if (av_frame_ref(dpacket->frame, frame)) {
|
||||
LOG_OOM();
|
||||
av_frame_free(&dframe->frame);
|
||||
av_frame_free(&dpacket->frame);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -27,9 +29,18 @@ sc_delayed_frame_init(struct sc_delayed_frame *dframe, const AVFrame *frame) {
|
||||
}
|
||||
|
||||
static void
|
||||
sc_delayed_frame_destroy(struct sc_delayed_frame *dframe) {
|
||||
av_frame_unref(dframe->frame);
|
||||
av_frame_free(&dframe->frame);
|
||||
sc_delayed_packet_init_session(struct sc_delayed_packet *dpacket,
|
||||
const struct sc_stream_session *session) {
|
||||
dpacket->type = SC_DELAYED_PACKET_TYPE_SESSION;
|
||||
dpacket->session = *session;
|
||||
}
|
||||
|
||||
static void
|
||||
sc_delayed_packet_destroy(struct sc_delayed_packet *dpacket) {
|
||||
if (dpacket->type == SC_DELAYED_PACKET_TYPE_FRAME) {
|
||||
av_frame_unref(dpacket->frame);
|
||||
av_frame_free(&dpacket->frame);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
@@ -50,43 +61,52 @@ run_buffering(void *data) {
|
||||
goto stopped;
|
||||
}
|
||||
|
||||
struct sc_delayed_frame dframe = sc_vecdeque_pop(&db->queue);
|
||||
struct sc_delayed_packet dpacket = sc_vecdeque_pop(&db->queue);
|
||||
|
||||
sc_tick max_deadline = sc_tick_now() + db->delay;
|
||||
// PTS (written by the server) are expressed in microseconds
|
||||
sc_tick pts = SC_TICK_FROM_US(dframe.frame->pts);
|
||||
bool ok;
|
||||
if (dpacket.type == SC_DELAYED_PACKET_TYPE_FRAME) {
|
||||
sc_tick max_deadline = sc_tick_now() + db->delay;
|
||||
// PTS (written by the server) are expressed in microseconds
|
||||
sc_tick pts = SC_TICK_FROM_US(dpacket.frame->pts);
|
||||
|
||||
bool timed_out = false;
|
||||
while (!db->stopped && !timed_out) {
|
||||
sc_tick deadline = sc_clock_to_system_time(&db->clock, pts)
|
||||
+ db->delay;
|
||||
if (deadline > max_deadline) {
|
||||
deadline = max_deadline;
|
||||
bool timed_out = false;
|
||||
while (!db->stopped && !timed_out) {
|
||||
sc_tick deadline = sc_clock_to_system_time(&db->clock, pts)
|
||||
+ db->delay;
|
||||
if (deadline > max_deadline) {
|
||||
deadline = max_deadline;
|
||||
}
|
||||
|
||||
timed_out =
|
||||
!sc_cond_timedwait(&db->wait_cond, &db->mutex, deadline);
|
||||
}
|
||||
|
||||
timed_out =
|
||||
!sc_cond_timedwait(&db->wait_cond, &db->mutex, deadline);
|
||||
}
|
||||
bool stopped = db->stopped;
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
|
||||
bool stopped = db->stopped;
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
|
||||
if (stopped) {
|
||||
sc_delayed_frame_destroy(&dframe);
|
||||
goto stopped;
|
||||
}
|
||||
if (stopped) {
|
||||
sc_delayed_packet_destroy(&dpacket);
|
||||
goto stopped;
|
||||
}
|
||||
|
||||
#ifdef SC_BUFFERING_DEBUG
|
||||
LOGD("Buffering: %" PRItick ";%" PRItick ";%" PRItick,
|
||||
pts, dframe.push_date, sc_tick_now());
|
||||
LOGD("Buffering: %" PRItick ";%" PRItick ";%" PRItick,
|
||||
pts, dframe.push_date, sc_tick_now());
|
||||
#endif
|
||||
|
||||
bool ok = sc_frame_source_sinks_push(&db->frame_source, dframe.frame);
|
||||
sc_delayed_frame_destroy(&dframe);
|
||||
ok = sc_frame_source_sinks_push(&db->frame_source, dpacket.frame);
|
||||
} else {
|
||||
assert(dpacket.type == SC_DELAYED_PACKET_TYPE_SESSION);
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
ok = sc_frame_source_sinks_push_session(&db->frame_source,
|
||||
&dpacket.session);
|
||||
}
|
||||
|
||||
sc_delayed_packet_destroy(&dpacket);
|
||||
if (!ok) {
|
||||
LOGE("Delayed frame could not be pushed, stopping");
|
||||
LOGE("Delayed packet could not be pushed, stopping");
|
||||
sc_mutex_lock(&db->mutex);
|
||||
// Prevent to push any new frame
|
||||
// Prevent to push any new packet
|
||||
db->stopped = true;
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
goto stopped;
|
||||
@@ -98,8 +118,8 @@ stopped:
|
||||
|
||||
// Flush queue
|
||||
while (!sc_vecdeque_is_empty(&db->queue)) {
|
||||
struct sc_delayed_frame *dframe = sc_vecdeque_popref(&db->queue);
|
||||
sc_delayed_frame_destroy(dframe);
|
||||
struct sc_delayed_packet *dpacket = sc_vecdeque_popref(&db->queue);
|
||||
sc_delayed_packet_destroy(dpacket);
|
||||
}
|
||||
|
||||
LOGD("Buffering thread ended");
|
||||
@@ -109,9 +129,11 @@ stopped:
|
||||
|
||||
static bool
|
||||
sc_delay_buffer_frame_sink_open(struct sc_frame_sink *sink,
|
||||
const AVCodecContext *ctx) {
|
||||
const AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session) {
|
||||
struct sc_delay_buffer *db = DOWNCAST(sink);
|
||||
(void) ctx;
|
||||
(void) session;
|
||||
|
||||
bool ok = sc_mutex_init(&db->mutex);
|
||||
if (!ok) {
|
||||
@@ -132,7 +154,7 @@ sc_delay_buffer_frame_sink_open(struct sc_frame_sink *sink,
|
||||
sc_vecdeque_init(&db->queue);
|
||||
db->stopped = false;
|
||||
|
||||
if (!sc_frame_source_sinks_open(&db->frame_source, ctx)) {
|
||||
if (!sc_frame_source_sinks_open(&db->frame_source, ctx, session)) {
|
||||
goto error_destroy_wait_cond;
|
||||
}
|
||||
|
||||
@@ -196,24 +218,56 @@ sc_delay_buffer_frame_sink_push(struct sc_frame_sink *sink,
|
||||
return sc_frame_source_sinks_push(&db->frame_source, frame);
|
||||
}
|
||||
|
||||
struct sc_delayed_frame dframe;
|
||||
bool ok = sc_delayed_frame_init(&dframe, frame);
|
||||
if (!ok) {
|
||||
struct sc_delayed_packet *dpacket = sc_vecdeque_push_hole(&db->queue);
|
||||
if (!dpacket) {
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
LOG_OOM();
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef SC_BUFFERING_DEBUG
|
||||
dframe.push_date = sc_tick_now();
|
||||
#endif
|
||||
|
||||
ok = sc_vecdeque_push(&db->queue, dframe);
|
||||
bool ok = sc_delayed_packet_init_frame(dpacket, frame);
|
||||
if (!ok) {
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
LOG_OOM();
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef SC_BUFFERING_DEBUG
|
||||
dpacket->push_date = sc_tick_now();
|
||||
#endif
|
||||
|
||||
sc_cond_signal(&db->queue_cond);
|
||||
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_delay_buffer_frame_sink_push_session(struct sc_frame_sink *sink,
|
||||
const struct sc_stream_session *session) {
|
||||
struct sc_delay_buffer *db = DOWNCAST(sink);
|
||||
|
||||
sc_mutex_lock(&db->mutex);
|
||||
|
||||
if (db->stopped) {
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct sc_delayed_packet *dpacket = sc_vecdeque_push_hole(&db->queue);
|
||||
if (!dpacket) {
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
LOG_OOM();
|
||||
return false;
|
||||
}
|
||||
|
||||
sc_delayed_packet_init_session(dpacket, session);
|
||||
|
||||
#ifdef SC_BUFFERING_DEBUG
|
||||
dpacket->push_date = sc_tick_now();
|
||||
#endif
|
||||
|
||||
sc_cond_signal(&db->queue_cond);
|
||||
|
||||
sc_mutex_unlock(&db->mutex);
|
||||
@@ -235,6 +289,7 @@ sc_delay_buffer_init(struct sc_delay_buffer *db, sc_tick delay,
|
||||
.open = sc_delay_buffer_frame_sink_open,
|
||||
.close = sc_delay_buffer_frame_sink_close,
|
||||
.push = sc_delay_buffer_frame_sink_push,
|
||||
.push_session = sc_delay_buffer_frame_sink_push_session,
|
||||
};
|
||||
|
||||
db->frame_sink.ops = &ops;
|
||||
|
||||
@@ -18,14 +18,23 @@
|
||||
// forward declarations
|
||||
typedef struct AVFrame AVFrame;
|
||||
|
||||
struct sc_delayed_frame {
|
||||
AVFrame *frame;
|
||||
enum sc_delayed_packet_type {
|
||||
SC_DELAYED_PACKET_TYPE_FRAME,
|
||||
SC_DELAYED_PACKET_TYPE_SESSION,
|
||||
};
|
||||
|
||||
struct sc_delayed_packet {
|
||||
enum sc_delayed_packet_type type;
|
||||
union {
|
||||
AVFrame *frame;
|
||||
struct sc_stream_session session;
|
||||
};
|
||||
#ifdef SC_BUFFERING_DEBUG
|
||||
sc_tick push_date;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct sc_delayed_frame_queue SC_VECDEQUE(struct sc_delayed_frame);
|
||||
struct sc_delayed_packet_queue SC_VECDEQUE(struct sc_delayed_packet);
|
||||
|
||||
struct sc_delay_buffer {
|
||||
struct sc_frame_source frame_source; // frame source trait
|
||||
@@ -40,7 +49,7 @@ struct sc_delay_buffer {
|
||||
sc_cond wait_cond;
|
||||
|
||||
struct sc_clock clock;
|
||||
struct sc_delayed_frame_queue queue;
|
||||
struct sc_delayed_packet_queue queue;
|
||||
bool stopped;
|
||||
};
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
|
||||
#define SC_PACKET_HEADER_SIZE 12
|
||||
|
||||
#define SC_PACKET_FLAG_CONFIG (UINT64_C(1) << 63)
|
||||
#define SC_PACKET_FLAG_KEY_FRAME (UINT64_C(1) << 62)
|
||||
#define SC_PACKET_FLAG_CONFIG (UINT64_C(1) << 62)
|
||||
#define SC_PACKET_FLAG_KEY_FRAME (UINT64_C(1) << 61)
|
||||
|
||||
#define SC_PACKET_PTS_MASK (SC_PACKET_FLAG_KEY_FRAME - 1)
|
||||
|
||||
@@ -63,48 +63,75 @@ sc_demuxer_recv_codec_id(struct sc_demuxer *demuxer, uint32_t *codec_id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_demuxer_recv_video_size(struct sc_demuxer *demuxer, uint32_t *width,
|
||||
uint32_t *height) {
|
||||
uint8_t data[8];
|
||||
ssize_t r = net_recv_all(demuxer->socket, data, 8);
|
||||
if (r < 8) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*width = sc_read32be(data);
|
||||
*height = sc_read32be(data + 4);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
|
||||
static inline bool
|
||||
sc_demuxer_recv_header(struct sc_demuxer *demuxer,
|
||||
uint8_t buf[static SC_PACKET_HEADER_SIZE]) {
|
||||
// The video and audio streams contain a sequence of raw packets (as
|
||||
// provided by MediaCodec), each prefixed with a "meta" header.
|
||||
//
|
||||
// The "meta" header length is 12 bytes:
|
||||
// The "meta" header length is 12 bytes.
|
||||
//
|
||||
//
|
||||
// If the MSB is 1, then it is a session packet (for a video stream only),
|
||||
// which only contains a 12-byte header:
|
||||
//
|
||||
// byte 0 byte 1 byte 2 byte 3
|
||||
// 10000000 00000000 00000000 00000000
|
||||
// ^<-------------------------------->
|
||||
// | padding
|
||||
// `- session packet flag
|
||||
//
|
||||
// byte 4 byte 5 byte 6 byte 7 byte 8 byte 9 byte 10 byte 11
|
||||
// ........ ........ ........ ........ ........ ........ ........ ........
|
||||
// <---------------------------------> <--------------------------------->
|
||||
// video width video height
|
||||
//
|
||||
//
|
||||
// If the MSB is 0, then it is a media packet, comprised of a 12-byte header
|
||||
// followed by <packet_size> bytes containing the packet/frame:
|
||||
//
|
||||
// [. . . . . . . .|. . . .]. . . . . . . . . . . . . . . ...
|
||||
// <-------------> <-----> <-----------------------------...
|
||||
// PTS packet raw packet
|
||||
// size
|
||||
//
|
||||
// It is followed by <packet_size> bytes containing the packet/frame.
|
||||
//
|
||||
// The most significant bits of the PTS are used for packet flags:
|
||||
//
|
||||
// byte 7 byte 6 byte 5 byte 4 byte 3 byte 2 byte 1 byte 0
|
||||
// CK...... ........ ........ ........ ........ ........ ........ ........
|
||||
// ^^<------------------------------------------------------------------->
|
||||
// || PTS
|
||||
// | `- key frame
|
||||
// `-- config packet
|
||||
// byte 0 byte 1 byte 2 byte 3 byte 4 byte 5 byte 6 byte 7
|
||||
// 0CK..... ........ ........ ........ ........ ........ ........ ........
|
||||
// ^^^<------------------------------------------------------------------>
|
||||
// ||| PTS
|
||||
// || `- key frame
|
||||
// | `-- config packet
|
||||
// `--- media packet flag
|
||||
//
|
||||
// byte 8 byte 9 byte 10 byte 11
|
||||
// ........ ........ ........ ........ ........ ........ . . .
|
||||
// <---------------------------------> <---------------- . . .
|
||||
// packet size raw packet
|
||||
//
|
||||
ssize_t r = net_recv_all(demuxer->socket, buf, SC_PACKET_HEADER_SIZE);
|
||||
assert(r <= SC_PACKET_HEADER_SIZE);
|
||||
return r == SC_PACKET_HEADER_SIZE;
|
||||
}
|
||||
|
||||
uint8_t header[SC_PACKET_HEADER_SIZE];
|
||||
ssize_t r = net_recv_all(demuxer->socket, header, SC_PACKET_HEADER_SIZE);
|
||||
if (r < SC_PACKET_HEADER_SIZE) {
|
||||
return false;
|
||||
}
|
||||
static bool
|
||||
sc_demuxer_is_session(const uint8_t *header) {
|
||||
return header[0] & 0x80;
|
||||
}
|
||||
|
||||
static void
|
||||
sc_demuxer_parse_session(const uint8_t *header,
|
||||
struct sc_stream_session *session) {
|
||||
assert(sc_demuxer_is_session(header));
|
||||
session->video.width = sc_read32be(&header[4]);
|
||||
session->video.height = sc_read32be(&header[8]);
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_demuxer_recv_packet(struct sc_demuxer *demuxer, const uint8_t *header,
|
||||
AVPacket *packet) {
|
||||
assert(!sc_demuxer_is_session(header));
|
||||
uint64_t pts_flags = sc_read64be(header);
|
||||
uint32_t len = sc_read32be(&header[8]);
|
||||
assert(len);
|
||||
@@ -114,7 +141,7 @@ sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
r = net_recv_all(demuxer->socket, packet->data, len);
|
||||
ssize_t r = net_recv_all(demuxer->socket, packet->data, len);
|
||||
if (r < 0 || ((uint32_t) r) < len) {
|
||||
av_packet_unref(packet);
|
||||
return false;
|
||||
@@ -187,17 +214,28 @@ run_demuxer(void *data) {
|
||||
|
||||
codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
|
||||
|
||||
uint8_t header[SC_PACKET_HEADER_SIZE];
|
||||
struct sc_stream_session session_data;
|
||||
|
||||
struct sc_stream_session *session = NULL;
|
||||
if (codec->type == AVMEDIA_TYPE_VIDEO) {
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
ok = sc_demuxer_recv_video_size(demuxer, &width, &height);
|
||||
bool ok = sc_demuxer_recv_header(demuxer, header);
|
||||
if (!ok) {
|
||||
goto finally_free_context;
|
||||
}
|
||||
|
||||
codec_ctx->width = width;
|
||||
codec_ctx->height = height;
|
||||
if (!sc_demuxer_is_session(header)) {
|
||||
LOGE("Unexpected packet (not a session header)");
|
||||
goto finally_free_context;
|
||||
}
|
||||
|
||||
session = &session_data;
|
||||
sc_demuxer_parse_session(header, session);
|
||||
|
||||
codec_ctx->width = session_data.video.width;
|
||||
codec_ctx->height = session_data.video.height;
|
||||
codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
|
||||
|
||||
} else {
|
||||
// Hardcoded audio properties
|
||||
#ifdef SCRCPY_LAVU_HAS_CHLAYOUT
|
||||
@@ -219,7 +257,8 @@ run_demuxer(void *data) {
|
||||
goto finally_free_context;
|
||||
}
|
||||
|
||||
if (!sc_packet_source_sinks_open(&demuxer->packet_source, codec_ctx)) {
|
||||
if (!sc_packet_source_sinks_open(&demuxer->packet_source, codec_ctx,
|
||||
session)) {
|
||||
goto finally_free_context;
|
||||
}
|
||||
|
||||
@@ -241,27 +280,39 @@ run_demuxer(void *data) {
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
bool ok = sc_demuxer_recv_packet(demuxer, packet);
|
||||
bool ok = sc_demuxer_recv_header(demuxer, header);
|
||||
if (!ok) {
|
||||
// end of stream
|
||||
status = SC_DEMUXER_STATUS_EOS;
|
||||
break;
|
||||
}
|
||||
|
||||
if (must_merge_config_packet) {
|
||||
// Prepend any config packet to the next media packet
|
||||
ok = sc_packet_merger_merge(&merger, packet);
|
||||
if (sc_demuxer_is_session(header)) {
|
||||
sc_demuxer_parse_session(header, &session_data);
|
||||
ok = sc_packet_source_sinks_push_session(&demuxer->packet_source,
|
||||
&session_data);
|
||||
if (!ok) {
|
||||
av_packet_unref(packet);
|
||||
// The sink already logged its concrete error
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sc_demuxer_recv_packet(demuxer, header, packet);
|
||||
|
||||
ok = sc_packet_source_sinks_push(&demuxer->packet_source, packet);
|
||||
av_packet_unref(packet);
|
||||
if (!ok) {
|
||||
// The sink already logged its concrete error
|
||||
break;
|
||||
if (must_merge_config_packet) {
|
||||
// Prepend any config packet to the next media packet
|
||||
ok = sc_packet_merger_merge(&merger, packet);
|
||||
if (!ok) {
|
||||
av_packet_unref(packet);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ok = sc_packet_source_sinks_push(&demuxer->packet_source, packet);
|
||||
av_packet_unref(packet);
|
||||
if (!ok) {
|
||||
// The sink already logged its concrete error
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ sc_device_msg_deserialize(const uint8_t *buf, size_t len,
|
||||
}
|
||||
uint16_t id = sc_read16be(&buf[1]);
|
||||
size_t size = sc_read16be(&buf[3]);
|
||||
if (size < len - 5) {
|
||||
if (size > len - 5) {
|
||||
return 0; // not available
|
||||
}
|
||||
uint8_t *data = malloc(size);
|
||||
|
||||
@@ -12,8 +12,11 @@ sc_display_init_novideo_icon(struct sc_display *display,
|
||||
SDL_Surface *icon_novideo) {
|
||||
assert(icon_novideo);
|
||||
|
||||
if (SDL_RenderSetLogicalSize(display->renderer,
|
||||
icon_novideo->w, icon_novideo->h)) {
|
||||
bool ok = SDL_SetRenderLogicalPresentation(display->renderer,
|
||||
icon_novideo->w,
|
||||
icon_novideo->h,
|
||||
SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
||||
if (!ok) {
|
||||
LOGW("Could not set renderer logical size: %s", SDL_GetError());
|
||||
// don't fail
|
||||
}
|
||||
@@ -31,16 +34,13 @@ sc_display_init_novideo_icon(struct sc_display *display,
|
||||
bool
|
||||
sc_display_init(struct sc_display *display, SDL_Window *window,
|
||||
SDL_Surface *icon_novideo, bool mipmaps) {
|
||||
display->renderer =
|
||||
SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
||||
display->renderer = SDL_CreateRenderer(window, NULL);
|
||||
if (!display->renderer) {
|
||||
LOGE("Could not create renderer: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_RendererInfo renderer_info;
|
||||
int r = SDL_GetRendererInfo(display->renderer, &renderer_info);
|
||||
const char *renderer_name = r ? NULL : renderer_info.name;
|
||||
const char *renderer_name = SDL_GetRendererName(display->renderer);
|
||||
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
|
||||
|
||||
display->mipmaps = false;
|
||||
@@ -56,8 +56,11 @@ sc_display_init(struct sc_display *display, SDL_Window *window,
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
// Persuade macOS to give us something better than OpenGL 2.1.
|
||||
// If we create a Core Profile context, we get the best OpenGL version.
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,
|
||||
SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
bool ok = SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,
|
||||
SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
if (!ok) {
|
||||
LOGW("Could not set a GL Core Profile Context");
|
||||
}
|
||||
|
||||
LOGD("Creating OpenGL Core Profile context");
|
||||
display->gl_context = SDL_GL_CreateContext(window);
|
||||
@@ -94,14 +97,13 @@ sc_display_init(struct sc_display *display, SDL_Window *window,
|
||||
display->texture = NULL;
|
||||
display->pending.flags = 0;
|
||||
display->pending.frame = NULL;
|
||||
display->has_frame = false;
|
||||
|
||||
if (icon_novideo) {
|
||||
// Without video, set a static scrcpy icon as window content
|
||||
bool ok = sc_display_init_novideo_icon(display, icon_novideo);
|
||||
if (!ok) {
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
SDL_GL_DeleteContext(display->gl_context);
|
||||
SDL_GL_DestroyContext(display->gl_context);
|
||||
#endif
|
||||
SDL_DestroyRenderer(display->renderer);
|
||||
return false;
|
||||
@@ -117,7 +119,7 @@ sc_display_destroy(struct sc_display *display) {
|
||||
av_frame_free(&display->pending.frame);
|
||||
}
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
SDL_GL_DeleteContext(display->gl_context);
|
||||
SDL_GL_DestroyContext(display->gl_context);
|
||||
#endif
|
||||
if (display->texture) {
|
||||
SDL_DestroyTexture(display->texture);
|
||||
@@ -125,13 +127,63 @@ sc_display_destroy(struct sc_display *display) {
|
||||
SDL_DestroyRenderer(display->renderer);
|
||||
}
|
||||
|
||||
static enum SDL_Colorspace
|
||||
sc_display_to_sdl_color_space(enum AVColorSpace color_space,
|
||||
enum AVColorRange color_range) {
|
||||
bool full_range = color_range == AVCOL_RANGE_JPEG;
|
||||
|
||||
switch (color_space) {
|
||||
case AVCOL_SPC_BT709:
|
||||
case AVCOL_SPC_RGB:
|
||||
return full_range ? SDL_COLORSPACE_BT709_FULL
|
||||
: SDL_COLORSPACE_BT709_LIMITED;
|
||||
case AVCOL_SPC_BT470BG:
|
||||
case AVCOL_SPC_SMPTE170M:
|
||||
return full_range ? SDL_COLORSPACE_BT601_FULL
|
||||
: SDL_COLORSPACE_BT601_LIMITED;
|
||||
case AVCOL_SPC_BT2020_NCL:
|
||||
case AVCOL_SPC_BT2020_CL:
|
||||
return full_range ? SDL_COLORSPACE_BT2020_FULL
|
||||
: SDL_COLORSPACE_BT2020_LIMITED;
|
||||
default:
|
||||
return SDL_COLORSPACE_JPEG;
|
||||
}
|
||||
}
|
||||
|
||||
static SDL_Texture *
|
||||
sc_display_create_texture(struct sc_display *display,
|
||||
struct sc_size size) {
|
||||
struct sc_size size, enum AVColorSpace color_space,
|
||||
enum AVColorRange color_range) {
|
||||
SDL_PropertiesID props = SDL_CreateProperties();
|
||||
if (!props) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
enum SDL_Colorspace sdl_color_space =
|
||||
sc_display_to_sdl_color_space(color_space, color_range);
|
||||
|
||||
bool ok =
|
||||
SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER,
|
||||
SDL_PIXELFORMAT_YV12);
|
||||
ok &= SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER,
|
||||
SDL_TEXTUREACCESS_STREAMING);
|
||||
ok &= SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER,
|
||||
size.width);
|
||||
ok &= SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER,
|
||||
size.height);
|
||||
ok &= SDL_SetNumberProperty(props,
|
||||
SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER,
|
||||
sdl_color_space);
|
||||
|
||||
if (!ok) {
|
||||
LOGE("Could not set texture properties");
|
||||
SDL_DestroyProperties(props);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_Renderer *renderer = display->renderer;
|
||||
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
|
||||
SDL_TEXTUREACCESS_STREAMING,
|
||||
size.width, size.height);
|
||||
SDL_Texture *texture = SDL_CreateTextureWithProperties(renderer, props);
|
||||
SDL_DestroyProperties(props);
|
||||
if (!texture) {
|
||||
LOGD("Could not create texture: %s", SDL_GetError());
|
||||
return NULL;
|
||||
@@ -140,24 +192,49 @@ sc_display_create_texture(struct sc_display *display,
|
||||
if (display->mipmaps) {
|
||||
struct sc_opengl *gl = &display->gl;
|
||||
|
||||
SDL_GL_BindTexture(texture, NULL, NULL);
|
||||
SDL_PropertiesID props = SDL_GetTextureProperties(texture);
|
||||
if (!props) {
|
||||
LOGE("Could not get texture properties: %s", SDL_GetError());
|
||||
SDL_DestroyTexture(texture);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *renderer_name = SDL_GetRendererName(display->renderer);
|
||||
const char *key = !renderer_name || !strcmp(renderer_name, "opengl")
|
||||
? SDL_PROP_TEXTURE_OPENGL_TEXTURE_NUMBER
|
||||
: SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_NUMBER;
|
||||
|
||||
int64_t texture_id = SDL_GetNumberProperty(props, key, 0);
|
||||
SDL_DestroyProperties(props);
|
||||
if (!texture_id) {
|
||||
LOGE("Could not get texture id: %s", SDL_GetError());
|
||||
SDL_DestroyTexture(texture);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
assert(!(texture_id & ~0xFFFFFFFF)); // fits in uint32_t
|
||||
display->texture_id = texture_id;
|
||||
gl->BindTexture(GL_TEXTURE_2D, display->texture_id);
|
||||
|
||||
// Enable trilinear filtering for downscaling
|
||||
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
|
||||
GL_LINEAR_MIPMAP_LINEAR);
|
||||
gl->TexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.f);
|
||||
|
||||
SDL_GL_UnbindTexture(texture);
|
||||
gl->BindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
static inline void
|
||||
sc_display_set_pending_size(struct sc_display *display, struct sc_size size) {
|
||||
sc_display_set_pending_texture(struct sc_display *display,
|
||||
struct sc_size size,
|
||||
enum AVColorRange color_range) {
|
||||
assert(!display->texture);
|
||||
display->pending.size = size;
|
||||
display->pending.flags |= SC_DISPLAY_PENDING_FLAG_SIZE;
|
||||
display->pending.texture.size = size;
|
||||
display->pending.texture.color_range = color_range;
|
||||
display->pending.flags |= SC_DISPLAY_PENDING_FLAG_TEXTURE;
|
||||
}
|
||||
|
||||
static bool
|
||||
@@ -170,6 +247,7 @@ sc_display_set_pending_frame(struct sc_display *display, const AVFrame *frame) {
|
||||
}
|
||||
}
|
||||
|
||||
av_frame_unref(display->pending.frame);
|
||||
int r = av_frame_ref(display->pending.frame, frame);
|
||||
if (r) {
|
||||
LOGE("Could not ref frame: %d", r);
|
||||
@@ -181,22 +259,31 @@ sc_display_set_pending_frame(struct sc_display *display, const AVFrame *frame) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Forward declaration
|
||||
static bool
|
||||
sc_display_update_texture_internal(struct sc_display *display,
|
||||
const AVFrame *frame);
|
||||
|
||||
static bool
|
||||
sc_display_apply_pending(struct sc_display *display) {
|
||||
if (display->pending.flags & SC_DISPLAY_PENDING_FLAG_SIZE) {
|
||||
if (display->pending.flags & SC_DISPLAY_PENDING_FLAG_TEXTURE) {
|
||||
assert(!display->texture);
|
||||
display->texture =
|
||||
sc_display_create_texture(display, display->pending.size);
|
||||
sc_display_create_texture(display,
|
||||
display->pending.texture.size,
|
||||
display->pending.texture.color_space,
|
||||
display->pending.texture.color_range);
|
||||
if (!display->texture) {
|
||||
return false;
|
||||
}
|
||||
|
||||
display->pending.flags &= ~SC_DISPLAY_PENDING_FLAG_SIZE;
|
||||
display->pending.flags &= ~SC_DISPLAY_PENDING_FLAG_TEXTURE;
|
||||
}
|
||||
|
||||
if (display->pending.flags & SC_DISPLAY_PENDING_FLAG_FRAME) {
|
||||
assert(display->pending.frame);
|
||||
bool ok = sc_display_update_texture(display, display->pending.frame);
|
||||
bool ok = sc_display_update_texture_internal(display,
|
||||
display->pending.frame);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
@@ -209,15 +296,18 @@ sc_display_apply_pending(struct sc_display *display) {
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_display_set_texture_size_internal(struct sc_display *display,
|
||||
struct sc_size size) {
|
||||
sc_display_prepare_texture_internal(struct sc_display *display,
|
||||
struct sc_size size,
|
||||
enum AVColorSpace color_space,
|
||||
enum AVColorRange color_range) {
|
||||
assert(size.width && size.height);
|
||||
|
||||
if (display->texture) {
|
||||
SDL_DestroyTexture(display->texture);
|
||||
}
|
||||
|
||||
display->texture = sc_display_create_texture(display, size);
|
||||
display->texture =
|
||||
sc_display_create_texture(display, size, color_space, color_range);
|
||||
if (!display->texture) {
|
||||
return false;
|
||||
}
|
||||
@@ -227,10 +317,13 @@ sc_display_set_texture_size_internal(struct sc_display *display,
|
||||
}
|
||||
|
||||
enum sc_display_result
|
||||
sc_display_set_texture_size(struct sc_display *display, struct sc_size size) {
|
||||
bool ok = sc_display_set_texture_size_internal(display, size);
|
||||
sc_display_prepare_texture(struct sc_display *display, struct sc_size size,
|
||||
enum AVColorSpace color_space,
|
||||
enum AVColorRange color_range) {
|
||||
bool ok = sc_display_prepare_texture_internal(display, size, color_space,
|
||||
color_range);
|
||||
if (!ok) {
|
||||
sc_display_set_pending_size(display, size);
|
||||
sc_display_set_pending_texture(display, size, color_range);
|
||||
return SC_DISPLAY_RESULT_PENDING;
|
||||
|
||||
}
|
||||
@@ -238,38 +331,25 @@ sc_display_set_texture_size(struct sc_display *display, struct sc_size size) {
|
||||
return SC_DISPLAY_RESULT_OK;
|
||||
}
|
||||
|
||||
static SDL_YUV_CONVERSION_MODE
|
||||
sc_display_to_sdl_color_range(enum AVColorRange color_range) {
|
||||
return color_range == AVCOL_RANGE_JPEG ? SDL_YUV_CONVERSION_JPEG
|
||||
: SDL_YUV_CONVERSION_AUTOMATIC;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_display_update_texture_internal(struct sc_display *display,
|
||||
const AVFrame *frame) {
|
||||
if (!display->has_frame) {
|
||||
// First frame
|
||||
display->has_frame = true;
|
||||
|
||||
// Configure YUV color range conversion
|
||||
SDL_YUV_CONVERSION_MODE sdl_color_range =
|
||||
sc_display_to_sdl_color_range(frame->color_range);
|
||||
SDL_SetYUVConversionMode(sdl_color_range);
|
||||
}
|
||||
|
||||
int ret = SDL_UpdateYUVTexture(display->texture, NULL,
|
||||
bool ok = SDL_UpdateYUVTexture(display->texture, NULL,
|
||||
frame->data[0], frame->linesize[0],
|
||||
frame->data[1], frame->linesize[1],
|
||||
frame->data[2], frame->linesize[2]);
|
||||
if (ret) {
|
||||
if (!ok) {
|
||||
LOGD("Could not update texture: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (display->mipmaps) {
|
||||
SDL_GL_BindTexture(display->texture, NULL, NULL);
|
||||
assert(display->texture_id);
|
||||
struct sc_opengl *gl = &display->gl;
|
||||
|
||||
gl->BindTexture(GL_TEXTURE_2D, display->texture_id);
|
||||
display->gl.GenerateMipmap(GL_TEXTURE_2D);
|
||||
SDL_GL_UnbindTexture(display->texture);
|
||||
gl->BindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -294,7 +374,9 @@ sc_display_update_texture(struct sc_display *display, const AVFrame *frame) {
|
||||
enum sc_display_result
|
||||
sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
|
||||
enum sc_orientation orientation) {
|
||||
SDL_RenderClear(display->renderer);
|
||||
bool always_ok = SDL_RenderClear(display->renderer);
|
||||
(void) always_ok;
|
||||
assert(always_ok);
|
||||
|
||||
if (display->pending.flags) {
|
||||
bool ok = sc_display_apply_pending(display);
|
||||
@@ -307,8 +389,10 @@ sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
|
||||
SDL_Texture *texture = display->texture;
|
||||
|
||||
if (orientation == SC_ORIENTATION_0) {
|
||||
int ret = SDL_RenderCopy(renderer, texture, NULL, geometry);
|
||||
if (ret) {
|
||||
SDL_FRect frect;
|
||||
SDL_RectToFRect(geometry, &frect);
|
||||
bool ok = SDL_RenderTexture(renderer, texture, NULL, &frect);
|
||||
if (!ok) {
|
||||
LOGE("Could not render texture: %s", SDL_GetError());
|
||||
return SC_DISPLAY_RESULT_ERROR;
|
||||
}
|
||||
@@ -316,29 +400,29 @@ sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
|
||||
unsigned cw_rotation = sc_orientation_get_rotation(orientation);
|
||||
double angle = 90 * cw_rotation;
|
||||
|
||||
const SDL_Rect *dstrect = NULL;
|
||||
SDL_Rect rect;
|
||||
SDL_FRect frect;
|
||||
if (sc_orientation_is_swap(orientation)) {
|
||||
rect.x = geometry->x + (geometry->w - geometry->h) / 2;
|
||||
rect.y = geometry->y + (geometry->h - geometry->w) / 2;
|
||||
rect.w = geometry->h;
|
||||
rect.h = geometry->w;
|
||||
dstrect = ▭
|
||||
frect.x = geometry->x + (geometry->w - geometry->h) / 2.f;
|
||||
frect.y = geometry->y + (geometry->h - geometry->w) / 2.f;
|
||||
frect.w = geometry->h;
|
||||
frect.h = geometry->w;
|
||||
} else {
|
||||
dstrect = geometry;
|
||||
SDL_RectToFRect(geometry, &frect);
|
||||
}
|
||||
|
||||
SDL_RendererFlip flip = sc_orientation_is_mirror(orientation)
|
||||
SDL_FlipMode flip = sc_orientation_is_mirror(orientation)
|
||||
? SDL_FLIP_HORIZONTAL : 0;
|
||||
|
||||
int ret = SDL_RenderCopyEx(renderer, texture, NULL, dstrect, angle,
|
||||
NULL, flip);
|
||||
if (ret) {
|
||||
bool ok = SDL_RenderTextureRotated(renderer, texture, NULL, &frect,
|
||||
angle, NULL, flip);
|
||||
if (!ok) {
|
||||
LOGE("Could not render texture: %s", SDL_GetError());
|
||||
return SC_DISPLAY_RESULT_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_RenderPresent(display->renderer);
|
||||
always_ok = SDL_RenderPresent(display->renderer);
|
||||
assert(always_ok);
|
||||
|
||||
return SC_DISPLAY_RESULT_OK;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <libavutil/frame.h>
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "coords.h"
|
||||
#include "opengl.h"
|
||||
@@ -22,20 +22,23 @@ struct sc_display {
|
||||
|
||||
struct sc_opengl gl;
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
SDL_GLContext *gl_context;
|
||||
SDL_GLContext gl_context;
|
||||
#endif
|
||||
|
||||
bool mipmaps;
|
||||
uint32_t texture_id; // only set if mipmaps is enabled
|
||||
|
||||
struct {
|
||||
#define SC_DISPLAY_PENDING_FLAG_SIZE 1
|
||||
#define SC_DISPLAY_PENDING_FLAG_TEXTURE 1
|
||||
#define SC_DISPLAY_PENDING_FLAG_FRAME 2
|
||||
int8_t flags;
|
||||
struct sc_size size;
|
||||
struct {
|
||||
struct sc_size size;
|
||||
enum AVColorSpace color_space;
|
||||
enum AVColorRange color_range;
|
||||
} texture;
|
||||
AVFrame *frame;
|
||||
} pending;
|
||||
|
||||
bool has_frame;
|
||||
};
|
||||
|
||||
enum sc_display_result {
|
||||
@@ -52,7 +55,9 @@ void
|
||||
sc_display_destroy(struct sc_display *display);
|
||||
|
||||
enum sc_display_result
|
||||
sc_display_set_texture_size(struct sc_display *display, struct sc_size size);
|
||||
sc_display_prepare_texture(struct sc_display *display, struct sc_size size,
|
||||
enum AVColorSpace color_space,
|
||||
enum AVColorRange color_range);
|
||||
|
||||
enum sc_display_result
|
||||
sc_display_update_texture(struct sc_display *display, const AVFrame *frame);
|
||||
|
||||
@@ -9,11 +9,8 @@ bool
|
||||
sc_push_event_impl(uint32_t type, const char *name) {
|
||||
SDL_Event event;
|
||||
event.type = type;
|
||||
int ret = SDL_PushEvent(&event);
|
||||
// ret < 0: error (queue full)
|
||||
// ret == 0: event was filtered
|
||||
// ret == 1: success
|
||||
if (ret != 1) {
|
||||
bool ok = SDL_PushEvent(&event);
|
||||
if (!ok) {
|
||||
LOGE("Could not post %s event: %s", name, SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
@@ -30,34 +27,25 @@ sc_post_to_main_thread(sc_runnable_fn run, void *userdata) {
|
||||
.data2 = userdata,
|
||||
},
|
||||
};
|
||||
int ret = SDL_PushEvent(&event);
|
||||
// ret < 0: error (queue full)
|
||||
// ret == 0: event was filtered
|
||||
// ret == 1: success
|
||||
if (ret != 1) {
|
||||
if (ret == 0) {
|
||||
// if ret == 0, this is expected on exit, log in debug mode
|
||||
LOGD("Could not post runnable to main thread (filtered)");
|
||||
} else {
|
||||
assert(ret < 0);
|
||||
LOGW("Could not post runnable to main thread: %s", SDL_GetError());
|
||||
}
|
||||
bool ok = SDL_PushEvent(&event);
|
||||
if (!ok) {
|
||||
LOGW("Could not post runnable to main thread: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int SDLCALL
|
||||
static bool SDLCALL
|
||||
task_event_filter(void *userdata, SDL_Event *event) {
|
||||
(void) userdata;
|
||||
|
||||
if (event->type == SC_EVENT_RUN_ON_MAIN_THREAD) {
|
||||
// Reject this event type from now on
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
return 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <SDL2/SDL_events.h>
|
||||
#include <SDL3/SDL_events.h>
|
||||
|
||||
enum {
|
||||
SC_EVENT_NEW_FRAME = SDL_USEREVENT,
|
||||
SC_EVENT_NEW_FRAME = SDL_EVENT_USER,
|
||||
SC_EVENT_RUN_ON_MAIN_THREAD,
|
||||
SC_EVENT_DEVICE_DISCONNECTED,
|
||||
SC_EVENT_SERVER_CONNECTION_FAILED,
|
||||
@@ -16,7 +16,6 @@ enum {
|
||||
SC_EVENT_USB_DEVICE_DISCONNECTED,
|
||||
SC_EVENT_DEMUXER_ERROR,
|
||||
SC_EVENT_RECORDER_ERROR,
|
||||
SC_EVENT_SCREEN_INIT_SIZE,
|
||||
SC_EVENT_TIME_LIMIT_REACHED,
|
||||
SC_EVENT_CONTROLLER_ERROR,
|
||||
SC_EVENT_AOA_OPEN_ERROR,
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
#include <stdint.h>
|
||||
|
||||
// 1 byte for buttons + padding, 1 byte for X position, 1 byte for Y position,
|
||||
// 1 byte for wheel motion
|
||||
#define SC_HID_MOUSE_INPUT_SIZE 4
|
||||
// 1 byte for wheel motion, 1 byte for hozizontal scrolling
|
||||
#define SC_HID_MOUSE_INPUT_SIZE 5
|
||||
|
||||
/**
|
||||
* Mouse descriptor from the specification:
|
||||
@@ -75,6 +75,21 @@ static const uint8_t SC_HID_MOUSE_REPORT_DESC[] = {
|
||||
// Input (Data, Variable, Relative): 3 position bytes (X, Y, Wheel)
|
||||
0x81, 0x06,
|
||||
|
||||
// Usage Page (Consumer Page)
|
||||
0x05, 0x0C,
|
||||
// Usage(AC Pan)
|
||||
0x0A, 0x38, 0x02,
|
||||
// Logical Minimum (-127)
|
||||
0x15, 0x81,
|
||||
// Logical Maximum (127)
|
||||
0x25, 0x7F,
|
||||
// Report Size (8)
|
||||
0x75, 0x08,
|
||||
// Report Count (1)
|
||||
0x95, 0x01,
|
||||
// Input (Data, Variable, Relative): 1 byte (AC Pan)
|
||||
0x81, 0x06,
|
||||
|
||||
// End Collection
|
||||
0xC0,
|
||||
|
||||
@@ -151,6 +166,12 @@ sc_hid_buttons_from_buttons_state(uint8_t buttons_state) {
|
||||
return c;
|
||||
}
|
||||
|
||||
void
|
||||
sc_hid_mouse_init(struct sc_hid_mouse *hid) {
|
||||
hid->residual_hscroll = 0;
|
||||
hid->residual_vscroll = 0;
|
||||
}
|
||||
|
||||
void
|
||||
sc_hid_mouse_generate_input_from_motion(struct sc_hid_input *hid_input,
|
||||
const struct sc_mouse_motion_event *event) {
|
||||
@@ -160,7 +181,8 @@ sc_hid_mouse_generate_input_from_motion(struct sc_hid_input *hid_input,
|
||||
data[0] = sc_hid_buttons_from_buttons_state(event->buttons_state);
|
||||
data[1] = CLAMP(event->xrel, -127, 127);
|
||||
data[2] = CLAMP(event->yrel, -127, 127);
|
||||
data[3] = 0; // wheel coordinates only used for scrolling
|
||||
data[3] = 0; // no vertical scrolling
|
||||
data[4] = 0; // no horizontal scrolling
|
||||
}
|
||||
|
||||
void
|
||||
@@ -172,22 +194,42 @@ sc_hid_mouse_generate_input_from_click(struct sc_hid_input *hid_input,
|
||||
data[0] = sc_hid_buttons_from_buttons_state(event->buttons_state);
|
||||
data[1] = 0; // no x motion
|
||||
data[2] = 0; // no y motion
|
||||
data[3] = 0; // wheel coordinates only used for scrolling
|
||||
data[3] = 0; // no vertical scrolling
|
||||
data[4] = 0; // no horizontal scrolling
|
||||
}
|
||||
|
||||
void
|
||||
sc_hid_mouse_generate_input_from_scroll(struct sc_hid_input *hid_input,
|
||||
static int8_t
|
||||
consume_scroll_integer(float *scroll) {
|
||||
float value = CLAMP(*scroll, -127, 127);
|
||||
int8_t consume = value; // truncate towards 0
|
||||
float residual = value - consume;
|
||||
*scroll = residual;
|
||||
return consume;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_hid_mouse_generate_input_from_scroll(struct sc_hid_mouse *hid,
|
||||
struct sc_hid_input *hid_input,
|
||||
const struct sc_mouse_scroll_event *event) {
|
||||
sc_hid_mouse_input_init(hid_input);
|
||||
|
||||
hid->residual_hscroll += event->hscroll;
|
||||
hid->residual_vscroll += event->vscroll;
|
||||
int8_t hscroll = consume_scroll_integer(&hid->residual_hscroll);
|
||||
int8_t vscroll = consume_scroll_integer(&hid->residual_vscroll);
|
||||
|
||||
if (!hscroll && !vscroll) {
|
||||
// Not enough scrolling to inject a scroll event
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t *data = hid_input->data;
|
||||
data[0] = 0; // buttons state irrelevant (and unknown)
|
||||
data[1] = 0; // no x motion
|
||||
data[2] = 0; // no y motion
|
||||
// In practice, vscroll is always -1, 0 or 1, but in theory other values
|
||||
// are possible
|
||||
data[3] = CLAMP(event->vscroll, -127, 127);
|
||||
// Horizontal scrolling ignored
|
||||
data[3] = vscroll;
|
||||
data[4] = hscroll;
|
||||
return true;
|
||||
}
|
||||
|
||||
void sc_hid_mouse_generate_open(struct sc_hid_open *hid_open) {
|
||||
|
||||
@@ -8,6 +8,13 @@
|
||||
|
||||
#define SC_HID_ID_MOUSE 2
|
||||
|
||||
struct sc_hid_mouse {
|
||||
float residual_hscroll;
|
||||
float residual_vscroll;
|
||||
};
|
||||
|
||||
void sc_hid_mouse_init(struct sc_hid_mouse *hid);
|
||||
|
||||
void
|
||||
sc_hid_mouse_generate_open(struct sc_hid_open *hid_open);
|
||||
|
||||
@@ -22,8 +29,9 @@ void
|
||||
sc_hid_mouse_generate_input_from_click(struct sc_hid_input *hid_input,
|
||||
const struct sc_mouse_click_event *event);
|
||||
|
||||
void
|
||||
sc_hid_mouse_generate_input_from_scroll(struct sc_hid_input *hid_input,
|
||||
bool
|
||||
sc_hid_mouse_generate_input_from_scroll(struct sc_hid_mouse *hid,
|
||||
struct sc_hid_input *hid_input,
|
||||
const struct sc_mouse_scroll_event *event);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavutil/pixdesc.h>
|
||||
#include <libavutil/pixfmt.h>
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "util/env.h"
|
||||
@@ -156,13 +156,7 @@ free_ctx:
|
||||
return result;
|
||||
}
|
||||
|
||||
#if !SDL_VERSION_ATLEAST(2, 0, 10)
|
||||
// SDL_PixelFormatEnum has been introduced in SDL 2.0.10. Use int for older SDL
|
||||
// versions.
|
||||
typedef int SDL_PixelFormatEnum;
|
||||
#endif
|
||||
|
||||
static SDL_PixelFormatEnum
|
||||
static SDL_PixelFormat
|
||||
to_sdl_pixel_format(enum AVPixelFormat fmt) {
|
||||
switch (fmt) {
|
||||
case AV_PIX_FMT_RGB24: return SDL_PIXELFORMAT_RGB24;
|
||||
@@ -172,13 +166,11 @@ to_sdl_pixel_format(enum AVPixelFormat fmt) {
|
||||
case AV_PIX_FMT_ABGR: return SDL_PIXELFORMAT_ABGR32;
|
||||
case AV_PIX_FMT_BGRA: return SDL_PIXELFORMAT_BGRA32;
|
||||
case AV_PIX_FMT_RGB565BE: return SDL_PIXELFORMAT_RGB565;
|
||||
case AV_PIX_FMT_RGB555BE: return SDL_PIXELFORMAT_RGB555;
|
||||
case AV_PIX_FMT_RGB555BE: return SDL_PIXELFORMAT_XRGB1555;
|
||||
case AV_PIX_FMT_BGR565BE: return SDL_PIXELFORMAT_BGR565;
|
||||
case AV_PIX_FMT_BGR555BE: return SDL_PIXELFORMAT_BGR555;
|
||||
case AV_PIX_FMT_RGB444BE: return SDL_PIXELFORMAT_RGB444;
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 12)
|
||||
case AV_PIX_FMT_BGR444BE: return SDL_PIXELFORMAT_BGR444;
|
||||
#endif
|
||||
case AV_PIX_FMT_BGR555BE: return SDL_PIXELFORMAT_XBGR1555;
|
||||
case AV_PIX_FMT_RGB444BE: return SDL_PIXELFORMAT_XRGB4444;
|
||||
case AV_PIX_FMT_BGR444BE: return SDL_PIXELFORMAT_XBGR4444;
|
||||
case AV_PIX_FMT_PAL8: return SDL_PIXELFORMAT_INDEX8;
|
||||
default: return SDL_PIXELFORMAT_UNKNOWN;
|
||||
}
|
||||
@@ -203,20 +195,16 @@ load_from_path(const char *path) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
SDL_PixelFormatEnum format = to_sdl_pixel_format(frame->format);
|
||||
SDL_PixelFormat format = to_sdl_pixel_format(frame->format);
|
||||
if (format == SDL_PIXELFORMAT_UNKNOWN) {
|
||||
LOGE("Unsupported icon pixel format: %s (%d)", desc->name,
|
||||
frame->format);
|
||||
goto error;
|
||||
}
|
||||
|
||||
int bits_per_pixel = av_get_bits_per_pixel(desc);
|
||||
SDL_Surface *surface =
|
||||
SDL_CreateRGBSurfaceWithFormatFrom(frame->data[0],
|
||||
frame->width, frame->height,
|
||||
bits_per_pixel,
|
||||
frame->linesize[0],
|
||||
format);
|
||||
SDL_CreateSurfaceFrom(frame->width, frame->height, format,
|
||||
frame->data[0], frame->linesize[0]);
|
||||
|
||||
if (!surface) {
|
||||
LOGE("Could not create icon surface");
|
||||
@@ -248,17 +236,35 @@ load_from_path(const char *path) {
|
||||
#endif
|
||||
}
|
||||
|
||||
SDL_Palette *palette = surface->format->palette;
|
||||
assert(palette);
|
||||
int ret = SDL_SetPaletteColors(palette, colors, 0, 256);
|
||||
if (ret) {
|
||||
SDL_Palette *palette = SDL_CreateSurfacePalette(surface);
|
||||
if (!palette) {
|
||||
LOGE("Could not create palette");
|
||||
SDL_DestroySurface(surface);
|
||||
goto error;
|
||||
}
|
||||
|
||||
bool ok = SDL_SetPaletteColors(palette, colors, 0, 256);
|
||||
if (!ok) {
|
||||
LOGE("Could not set palette colors");
|
||||
SDL_FreeSurface(surface);
|
||||
SDL_DestroySurface(surface);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
surface->userdata = frame; // frame owns the data
|
||||
SDL_PropertiesID props = SDL_GetSurfaceProperties(surface);
|
||||
if (!props) {
|
||||
LOGE("Could not get surface properties: %s", SDL_GetError());
|
||||
SDL_DestroySurface(surface);
|
||||
goto error;
|
||||
}
|
||||
|
||||
// frame owns the data
|
||||
bool ok = SDL_SetPointerProperty(props, "sc_frame", frame);
|
||||
if (!ok) {
|
||||
LOGE("Could not get surface properties: %s", SDL_GetError());
|
||||
SDL_DestroySurface(surface);
|
||||
goto error;
|
||||
}
|
||||
|
||||
return surface;
|
||||
|
||||
@@ -281,8 +287,10 @@ scrcpy_icon_load(void) {
|
||||
|
||||
void
|
||||
scrcpy_icon_destroy(SDL_Surface *icon) {
|
||||
AVFrame *frame = icon->userdata;
|
||||
SDL_PropertiesID props = SDL_GetSurfaceProperties(icon);
|
||||
assert(props);
|
||||
AVFrame *frame = SDL_GetPointerProperty(props, "sc_frame", NULL);
|
||||
assert(frame);
|
||||
av_frame_free(&frame);
|
||||
SDL_FreeSurface(icon);
|
||||
SDL_DestroySurface(icon);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <SDL2/SDL_surface.h>
|
||||
#include <SDL3/SDL_surface.h>
|
||||
|
||||
SDL_Surface *
|
||||
scrcpy_icon_load(void);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <SDL2/SDL_events.h>
|
||||
#include <SDL3/SDL_events.h>
|
||||
|
||||
#include "coords.h"
|
||||
|
||||
@@ -43,17 +43,17 @@
|
||||
*/
|
||||
|
||||
enum sc_mod {
|
||||
SC_MOD_LSHIFT = KMOD_LSHIFT,
|
||||
SC_MOD_RSHIFT = KMOD_RSHIFT,
|
||||
SC_MOD_LCTRL = KMOD_LCTRL,
|
||||
SC_MOD_RCTRL = KMOD_RCTRL,
|
||||
SC_MOD_LALT = KMOD_LALT,
|
||||
SC_MOD_RALT = KMOD_RALT,
|
||||
SC_MOD_LGUI = KMOD_LGUI,
|
||||
SC_MOD_RGUI = KMOD_RGUI,
|
||||
SC_MOD_LSHIFT = SDL_KMOD_LSHIFT,
|
||||
SC_MOD_RSHIFT = SDL_KMOD_RSHIFT,
|
||||
SC_MOD_LCTRL = SDL_KMOD_LCTRL,
|
||||
SC_MOD_RCTRL = SDL_KMOD_RCTRL,
|
||||
SC_MOD_LALT = SDL_KMOD_LALT,
|
||||
SC_MOD_RALT = SDL_KMOD_RALT,
|
||||
SC_MOD_LGUI = SDL_KMOD_LGUI,
|
||||
SC_MOD_RGUI = SDL_KMOD_RGUI,
|
||||
|
||||
SC_MOD_NUM = KMOD_NUM,
|
||||
SC_MOD_CAPS = KMOD_CAPS,
|
||||
SC_MOD_NUM = SDL_KMOD_NUM,
|
||||
SC_MOD_CAPS = SDL_KMOD_CAPS,
|
||||
};
|
||||
|
||||
enum sc_action {
|
||||
@@ -70,12 +70,12 @@ enum sc_keycode {
|
||||
SC_KEYCODE_TAB = SDLK_TAB,
|
||||
SC_KEYCODE_SPACE = SDLK_SPACE,
|
||||
SC_KEYCODE_EXCLAIM = SDLK_EXCLAIM,
|
||||
SC_KEYCODE_QUOTEDBL = SDLK_QUOTEDBL,
|
||||
SC_KEYCODE_QUOTEDBL = SDLK_DBLAPOSTROPHE,
|
||||
SC_KEYCODE_HASH = SDLK_HASH,
|
||||
SC_KEYCODE_PERCENT = SDLK_PERCENT,
|
||||
SC_KEYCODE_DOLLAR = SDLK_DOLLAR,
|
||||
SC_KEYCODE_AMPERSAND = SDLK_AMPERSAND,
|
||||
SC_KEYCODE_QUOTE = SDLK_QUOTE,
|
||||
SC_KEYCODE_QUOTE = SDLK_APOSTROPHE,
|
||||
SC_KEYCODE_LEFTPAREN = SDLK_LEFTPAREN,
|
||||
SC_KEYCODE_RIGHTPAREN = SDLK_RIGHTPAREN,
|
||||
SC_KEYCODE_ASTERISK = SDLK_ASTERISK,
|
||||
@@ -107,33 +107,33 @@ enum sc_keycode {
|
||||
SC_KEYCODE_RIGHTBRACKET = SDLK_RIGHTBRACKET,
|
||||
SC_KEYCODE_CARET = SDLK_CARET,
|
||||
SC_KEYCODE_UNDERSCORE = SDLK_UNDERSCORE,
|
||||
SC_KEYCODE_BACKQUOTE = SDLK_BACKQUOTE,
|
||||
SC_KEYCODE_a = SDLK_a,
|
||||
SC_KEYCODE_b = SDLK_b,
|
||||
SC_KEYCODE_c = SDLK_c,
|
||||
SC_KEYCODE_d = SDLK_d,
|
||||
SC_KEYCODE_e = SDLK_e,
|
||||
SC_KEYCODE_f = SDLK_f,
|
||||
SC_KEYCODE_g = SDLK_g,
|
||||
SC_KEYCODE_h = SDLK_h,
|
||||
SC_KEYCODE_i = SDLK_i,
|
||||
SC_KEYCODE_j = SDLK_j,
|
||||
SC_KEYCODE_k = SDLK_k,
|
||||
SC_KEYCODE_l = SDLK_l,
|
||||
SC_KEYCODE_m = SDLK_m,
|
||||
SC_KEYCODE_n = SDLK_n,
|
||||
SC_KEYCODE_o = SDLK_o,
|
||||
SC_KEYCODE_p = SDLK_p,
|
||||
SC_KEYCODE_q = SDLK_q,
|
||||
SC_KEYCODE_r = SDLK_r,
|
||||
SC_KEYCODE_s = SDLK_s,
|
||||
SC_KEYCODE_t = SDLK_t,
|
||||
SC_KEYCODE_u = SDLK_u,
|
||||
SC_KEYCODE_v = SDLK_v,
|
||||
SC_KEYCODE_w = SDLK_w,
|
||||
SC_KEYCODE_x = SDLK_x,
|
||||
SC_KEYCODE_y = SDLK_y,
|
||||
SC_KEYCODE_z = SDLK_z,
|
||||
SC_KEYCODE_BACKQUOTE = SDLK_GRAVE,
|
||||
SC_KEYCODE_a = SDLK_A,
|
||||
SC_KEYCODE_b = SDLK_B,
|
||||
SC_KEYCODE_c = SDLK_C,
|
||||
SC_KEYCODE_d = SDLK_D,
|
||||
SC_KEYCODE_e = SDLK_E,
|
||||
SC_KEYCODE_f = SDLK_F,
|
||||
SC_KEYCODE_g = SDLK_G,
|
||||
SC_KEYCODE_h = SDLK_H,
|
||||
SC_KEYCODE_i = SDLK_I,
|
||||
SC_KEYCODE_j = SDLK_J,
|
||||
SC_KEYCODE_k = SDLK_K,
|
||||
SC_KEYCODE_l = SDLK_L,
|
||||
SC_KEYCODE_m = SDLK_M,
|
||||
SC_KEYCODE_n = SDLK_N,
|
||||
SC_KEYCODE_o = SDLK_O,
|
||||
SC_KEYCODE_p = SDLK_P,
|
||||
SC_KEYCODE_q = SDLK_Q,
|
||||
SC_KEYCODE_r = SDLK_R,
|
||||
SC_KEYCODE_s = SDLK_S,
|
||||
SC_KEYCODE_t = SDLK_T,
|
||||
SC_KEYCODE_u = SDLK_U,
|
||||
SC_KEYCODE_v = SDLK_V,
|
||||
SC_KEYCODE_w = SDLK_W,
|
||||
SC_KEYCODE_x = SDLK_X,
|
||||
SC_KEYCODE_y = SDLK_Y,
|
||||
SC_KEYCODE_z = SDLK_Z,
|
||||
|
||||
SC_KEYCODE_CAPSLOCK = SDLK_CAPSLOCK,
|
||||
|
||||
@@ -315,11 +315,11 @@ enum sc_scancode {
|
||||
// to avoid unnecessary conversions (and confusion).
|
||||
enum sc_mouse_button {
|
||||
SC_MOUSE_BUTTON_UNKNOWN = 0,
|
||||
SC_MOUSE_BUTTON_LEFT = SDL_BUTTON(SDL_BUTTON_LEFT),
|
||||
SC_MOUSE_BUTTON_RIGHT = SDL_BUTTON(SDL_BUTTON_RIGHT),
|
||||
SC_MOUSE_BUTTON_MIDDLE = SDL_BUTTON(SDL_BUTTON_MIDDLE),
|
||||
SC_MOUSE_BUTTON_X1 = SDL_BUTTON(SDL_BUTTON_X1),
|
||||
SC_MOUSE_BUTTON_X2 = SDL_BUTTON(SDL_BUTTON_X2),
|
||||
SC_MOUSE_BUTTON_LEFT = SDL_BUTTON_MASK(SDL_BUTTON_LEFT),
|
||||
SC_MOUSE_BUTTON_RIGHT = SDL_BUTTON_MASK(SDL_BUTTON_RIGHT),
|
||||
SC_MOUSE_BUTTON_MIDDLE = SDL_BUTTON_MASK(SDL_BUTTON_MIDDLE),
|
||||
SC_MOUSE_BUTTON_X1 = SDL_BUTTON_MASK(SDL_BUTTON_X1),
|
||||
SC_MOUSE_BUTTON_X2 = SDL_BUTTON_MASK(SDL_BUTTON_X2),
|
||||
};
|
||||
|
||||
// Use the naming from SDL3 for gamepad axis and buttons:
|
||||
@@ -327,31 +327,31 @@ enum sc_mouse_button {
|
||||
|
||||
enum sc_gamepad_axis {
|
||||
SC_GAMEPAD_AXIS_UNKNOWN = -1,
|
||||
SC_GAMEPAD_AXIS_LEFTX = SDL_CONTROLLER_AXIS_LEFTX,
|
||||
SC_GAMEPAD_AXIS_LEFTY = SDL_CONTROLLER_AXIS_LEFTY,
|
||||
SC_GAMEPAD_AXIS_RIGHTX = SDL_CONTROLLER_AXIS_RIGHTX,
|
||||
SC_GAMEPAD_AXIS_RIGHTY = SDL_CONTROLLER_AXIS_RIGHTY,
|
||||
SC_GAMEPAD_AXIS_LEFT_TRIGGER = SDL_CONTROLLER_AXIS_TRIGGERLEFT,
|
||||
SC_GAMEPAD_AXIS_RIGHT_TRIGGER = SDL_CONTROLLER_AXIS_TRIGGERRIGHT,
|
||||
SC_GAMEPAD_AXIS_LEFTX = SDL_GAMEPAD_AXIS_LEFTX,
|
||||
SC_GAMEPAD_AXIS_LEFTY = SDL_GAMEPAD_AXIS_LEFTY,
|
||||
SC_GAMEPAD_AXIS_RIGHTX = SDL_GAMEPAD_AXIS_RIGHTX,
|
||||
SC_GAMEPAD_AXIS_RIGHTY = SDL_GAMEPAD_AXIS_RIGHTY,
|
||||
SC_GAMEPAD_AXIS_LEFT_TRIGGER = SDL_GAMEPAD_AXIS_LEFT_TRIGGER,
|
||||
SC_GAMEPAD_AXIS_RIGHT_TRIGGER = SDL_GAMEPAD_AXIS_RIGHT_TRIGGER,
|
||||
};
|
||||
|
||||
enum sc_gamepad_button {
|
||||
SC_GAMEPAD_BUTTON_UNKNOWN = -1,
|
||||
SC_GAMEPAD_BUTTON_SOUTH = SDL_CONTROLLER_BUTTON_A,
|
||||
SC_GAMEPAD_BUTTON_EAST = SDL_CONTROLLER_BUTTON_B,
|
||||
SC_GAMEPAD_BUTTON_WEST = SDL_CONTROLLER_BUTTON_X,
|
||||
SC_GAMEPAD_BUTTON_NORTH = SDL_CONTROLLER_BUTTON_Y,
|
||||
SC_GAMEPAD_BUTTON_BACK = SDL_CONTROLLER_BUTTON_BACK,
|
||||
SC_GAMEPAD_BUTTON_GUIDE = SDL_CONTROLLER_BUTTON_GUIDE,
|
||||
SC_GAMEPAD_BUTTON_START = SDL_CONTROLLER_BUTTON_START,
|
||||
SC_GAMEPAD_BUTTON_LEFT_STICK = SDL_CONTROLLER_BUTTON_LEFTSTICK,
|
||||
SC_GAMEPAD_BUTTON_RIGHT_STICK = SDL_CONTROLLER_BUTTON_RIGHTSTICK,
|
||||
SC_GAMEPAD_BUTTON_LEFT_SHOULDER = SDL_CONTROLLER_BUTTON_LEFTSHOULDER,
|
||||
SC_GAMEPAD_BUTTON_RIGHT_SHOULDER = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER,
|
||||
SC_GAMEPAD_BUTTON_DPAD_UP = SDL_CONTROLLER_BUTTON_DPAD_UP,
|
||||
SC_GAMEPAD_BUTTON_DPAD_DOWN = SDL_CONTROLLER_BUTTON_DPAD_DOWN,
|
||||
SC_GAMEPAD_BUTTON_DPAD_LEFT = SDL_CONTROLLER_BUTTON_DPAD_LEFT,
|
||||
SC_GAMEPAD_BUTTON_DPAD_RIGHT = SDL_CONTROLLER_BUTTON_DPAD_RIGHT,
|
||||
SC_GAMEPAD_BUTTON_SOUTH = SDL_GAMEPAD_BUTTON_SOUTH,
|
||||
SC_GAMEPAD_BUTTON_EAST = SDL_GAMEPAD_BUTTON_EAST,
|
||||
SC_GAMEPAD_BUTTON_WEST = SDL_GAMEPAD_BUTTON_WEST,
|
||||
SC_GAMEPAD_BUTTON_NORTH = SDL_GAMEPAD_BUTTON_NORTH,
|
||||
SC_GAMEPAD_BUTTON_BACK = SDL_GAMEPAD_BUTTON_BACK,
|
||||
SC_GAMEPAD_BUTTON_GUIDE = SDL_GAMEPAD_BUTTON_GUIDE,
|
||||
SC_GAMEPAD_BUTTON_START = SDL_GAMEPAD_BUTTON_START,
|
||||
SC_GAMEPAD_BUTTON_LEFT_STICK = SDL_GAMEPAD_BUTTON_LEFT_STICK,
|
||||
SC_GAMEPAD_BUTTON_RIGHT_STICK = SDL_GAMEPAD_BUTTON_RIGHT_STICK,
|
||||
SC_GAMEPAD_BUTTON_LEFT_SHOULDER = SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,
|
||||
SC_GAMEPAD_BUTTON_RIGHT_SHOULDER = SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,
|
||||
SC_GAMEPAD_BUTTON_DPAD_UP = SDL_GAMEPAD_BUTTON_DPAD_UP,
|
||||
SC_GAMEPAD_BUTTON_DPAD_DOWN = SDL_GAMEPAD_BUTTON_DPAD_DOWN,
|
||||
SC_GAMEPAD_BUTTON_DPAD_LEFT = SDL_GAMEPAD_BUTTON_DPAD_LEFT,
|
||||
SC_GAMEPAD_BUTTON_DPAD_RIGHT = SDL_GAMEPAD_BUTTON_DPAD_RIGHT,
|
||||
};
|
||||
|
||||
static_assert(sizeof(enum sc_mod) >= sizeof(SDL_Keymod),
|
||||
@@ -449,8 +449,8 @@ sc_scancode_from_sdl(SDL_Scancode scancode) {
|
||||
|
||||
static inline enum sc_action
|
||||
sc_action_from_sdl_keyboard_type(uint32_t type) {
|
||||
assert(type == SDL_KEYDOWN || type == SDL_KEYUP);
|
||||
if (type == SDL_KEYDOWN) {
|
||||
assert(type == SDL_EVENT_KEY_DOWN || type == SDL_EVENT_KEY_UP);
|
||||
if (type == SDL_EVENT_KEY_DOWN) {
|
||||
return SC_ACTION_DOWN;
|
||||
}
|
||||
return SC_ACTION_UP;
|
||||
@@ -458,8 +458,8 @@ sc_action_from_sdl_keyboard_type(uint32_t type) {
|
||||
|
||||
static inline enum sc_action
|
||||
sc_action_from_sdl_mousebutton_type(uint32_t type) {
|
||||
assert(type == SDL_MOUSEBUTTONDOWN || type == SDL_MOUSEBUTTONUP);
|
||||
if (type == SDL_MOUSEBUTTONDOWN) {
|
||||
assert(type == SDL_EVENT_MOUSE_BUTTON_DOWN || type == SDL_EVENT_MOUSE_BUTTON_UP);
|
||||
if (type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
|
||||
return SC_ACTION_DOWN;
|
||||
}
|
||||
return SC_ACTION_UP;
|
||||
@@ -467,12 +467,12 @@ sc_action_from_sdl_mousebutton_type(uint32_t type) {
|
||||
|
||||
static inline enum sc_touch_action
|
||||
sc_touch_action_from_sdl(uint32_t type) {
|
||||
assert(type == SDL_FINGERMOTION || type == SDL_FINGERDOWN ||
|
||||
type == SDL_FINGERUP);
|
||||
if (type == SDL_FINGERMOTION) {
|
||||
assert(type == SDL_EVENT_FINGER_MOTION || type == SDL_EVENT_FINGER_DOWN ||
|
||||
type == SDL_EVENT_FINGER_UP);
|
||||
if (type == SDL_EVENT_FINGER_MOTION) {
|
||||
return SC_TOUCH_ACTION_MOVE;
|
||||
}
|
||||
if (type == SDL_FINGERDOWN) {
|
||||
if (type == SDL_EVENT_FINGER_DOWN) {
|
||||
return SC_TOUCH_ACTION_DOWN;
|
||||
}
|
||||
return SC_TOUCH_ACTION_UP;
|
||||
@@ -482,7 +482,7 @@ static inline enum sc_mouse_button
|
||||
sc_mouse_button_from_sdl(uint8_t button) {
|
||||
if (button >= SDL_BUTTON_LEFT && button <= SDL_BUTTON_X2) {
|
||||
// SC_MOUSE_BUTTON_* constants are initialized from SDL_BUTTON(index)
|
||||
return SDL_BUTTON(button);
|
||||
return SDL_BUTTON_MASK(button);
|
||||
}
|
||||
|
||||
return SC_MOUSE_BUTTON_UNKNOWN;
|
||||
@@ -498,7 +498,7 @@ sc_mouse_buttons_state_from_sdl(uint32_t buttons_state) {
|
||||
|
||||
static inline enum sc_gamepad_axis
|
||||
sc_gamepad_axis_from_sdl(uint8_t axis) {
|
||||
if (axis <= SDL_CONTROLLER_AXIS_TRIGGERRIGHT) {
|
||||
if (axis <= SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) {
|
||||
// SC_GAMEPAD_AXIS_* constants are initialized from
|
||||
// SDL_CONTROLLER_AXIS_*
|
||||
return axis;
|
||||
@@ -508,7 +508,7 @@ sc_gamepad_axis_from_sdl(uint8_t axis) {
|
||||
|
||||
static inline enum sc_gamepad_button
|
||||
sc_gamepad_button_from_sdl(uint8_t button) {
|
||||
if (button <= SDL_CONTROLLER_BUTTON_DPAD_RIGHT) {
|
||||
if (button <= SDL_GAMEPAD_BUTTON_DPAD_RIGHT) {
|
||||
// SC_GAMEPAD_BUTTON_* constants are initialized from
|
||||
// SDL_CONTROLLER_BUTTON_*
|
||||
return button;
|
||||
@@ -518,8 +518,8 @@ sc_gamepad_button_from_sdl(uint8_t button) {
|
||||
|
||||
static inline enum sc_action
|
||||
sc_action_from_sdl_controllerbutton_type(uint32_t type) {
|
||||
assert(type == SDL_CONTROLLERBUTTONDOWN || type == SDL_CONTROLLERBUTTONUP);
|
||||
if (type == SDL_CONTROLLERBUTTONDOWN) {
|
||||
assert(type == SDL_EVENT_GAMEPAD_BUTTON_DOWN || type == SDL_EVENT_GAMEPAD_BUTTON_UP);
|
||||
if (type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
|
||||
return SC_ACTION_DOWN;
|
||||
}
|
||||
return SC_ACTION_UP;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "android/input.h"
|
||||
#include "android/keycodes.h"
|
||||
@@ -374,11 +374,11 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
bool paused = im->screen->paused;
|
||||
bool video = im->screen->video;
|
||||
|
||||
SDL_Keycode sdl_keycode = event->keysym.sym;
|
||||
uint16_t mod = event->keysym.mod;
|
||||
bool down = event->type == SDL_KEYDOWN;
|
||||
bool ctrl = event->keysym.mod & KMOD_CTRL;
|
||||
bool shift = event->keysym.mod & KMOD_SHIFT;
|
||||
SDL_Keycode sdl_keycode = event->key;
|
||||
uint16_t mod = event->mod;
|
||||
bool down = event->type == SDL_EVENT_KEY_DOWN;
|
||||
bool ctrl = event->mod & SDL_KMOD_CTRL;
|
||||
bool shift = event->mod & SDL_KMOD_SHIFT;
|
||||
bool repeat = event->repeat;
|
||||
|
||||
// Either the modifier includes a shortcut modifier, or the key
|
||||
@@ -402,39 +402,39 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
if (is_shortcut) {
|
||||
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
|
||||
switch (sdl_keycode) {
|
||||
case SDLK_h:
|
||||
case SDLK_H:
|
||||
if (im->kp && !shift && !repeat && !paused) {
|
||||
action_home(im, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_b: // fall-through
|
||||
case SDLK_B: // fall-through
|
||||
case SDLK_BACKSPACE:
|
||||
if (im->kp && !shift && !repeat && !paused) {
|
||||
action_back(im, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_s:
|
||||
case SDLK_S:
|
||||
if (im->kp && !shift && !repeat && !paused) {
|
||||
action_app_switch(im, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_m:
|
||||
case SDLK_M:
|
||||
if (im->kp && !shift && !repeat && !paused) {
|
||||
action_menu(im, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_p:
|
||||
case SDLK_P:
|
||||
if (im->kp && !shift && !repeat && !paused) {
|
||||
action_power(im, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_o:
|
||||
case SDLK_O:
|
||||
if (control && !repeat && down && !paused) {
|
||||
bool on = shift;
|
||||
set_display_power(im, on);
|
||||
}
|
||||
return;
|
||||
case SDLK_z:
|
||||
case SDLK_Z:
|
||||
if (video && down && !repeat) {
|
||||
sc_screen_set_paused(im->screen, !shift);
|
||||
}
|
||||
@@ -483,17 +483,17 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
}
|
||||
}
|
||||
return;
|
||||
case SDLK_c:
|
||||
case SDLK_C:
|
||||
if (im->kp && !shift && !repeat && down && !paused) {
|
||||
get_device_clipboard(im, SC_COPY_KEY_COPY);
|
||||
}
|
||||
return;
|
||||
case SDLK_x:
|
||||
case SDLK_X:
|
||||
if (im->kp && !shift && !repeat && down && !paused) {
|
||||
get_device_clipboard(im, SC_COPY_KEY_CUT);
|
||||
}
|
||||
return;
|
||||
case SDLK_v:
|
||||
case SDLK_V:
|
||||
if (im->kp && !repeat && down && !paused) {
|
||||
if (shift || im->legacy_paste) {
|
||||
// inject the text as input events
|
||||
@@ -505,27 +505,27 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
}
|
||||
}
|
||||
return;
|
||||
case SDLK_f:
|
||||
case SDLK_F:
|
||||
if (video && !shift && !repeat && down) {
|
||||
sc_screen_toggle_fullscreen(im->screen);
|
||||
}
|
||||
return;
|
||||
case SDLK_w:
|
||||
case SDLK_W:
|
||||
if (video && !shift && !repeat && down) {
|
||||
sc_screen_resize_to_fit(im->screen);
|
||||
}
|
||||
return;
|
||||
case SDLK_g:
|
||||
case SDLK_G:
|
||||
if (video && !shift && !repeat && down) {
|
||||
sc_screen_resize_to_pixel_perfect(im->screen);
|
||||
}
|
||||
return;
|
||||
case SDLK_i:
|
||||
case SDLK_I:
|
||||
if (video && !shift && !repeat && down) {
|
||||
switch_fps_counter_state(im);
|
||||
}
|
||||
return;
|
||||
case SDLK_n:
|
||||
case SDLK_N:
|
||||
if (control && !repeat && down && !paused) {
|
||||
if (shift) {
|
||||
collapse_panels(im);
|
||||
@@ -536,7 +536,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
}
|
||||
}
|
||||
return;
|
||||
case SDLK_r:
|
||||
case SDLK_R:
|
||||
if (control && !repeat && down && !paused) {
|
||||
if (shift) {
|
||||
reset_video(im);
|
||||
@@ -545,7 +545,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
}
|
||||
}
|
||||
return;
|
||||
case SDLK_k:
|
||||
case SDLK_K:
|
||||
if (control && !shift && !repeat && down && !paused
|
||||
&& im->kp && im->kp->hid) {
|
||||
// Only if the current keyboard is hid
|
||||
@@ -562,7 +562,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
}
|
||||
|
||||
uint64_t ack_to_wait = SC_SEQUENCE_INVALID;
|
||||
bool is_ctrl_v = ctrl && !shift && sdl_keycode == SDLK_v && down && !repeat;
|
||||
bool is_ctrl_v = ctrl && !shift && sdl_keycode == SDLK_V && down && !repeat;
|
||||
if (im->clipboard_autosync && is_ctrl_v) {
|
||||
if (im->legacy_paste) {
|
||||
// inject the text as input events
|
||||
@@ -595,7 +595,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
return;
|
||||
}
|
||||
|
||||
enum sc_scancode scancode = sc_scancode_from_sdl(event->keysym.scancode);
|
||||
enum sc_scancode scancode = sc_scancode_from_sdl(event->scancode);
|
||||
if (scancode == SC_SCANCODE_UNKNOWN) {
|
||||
return;
|
||||
}
|
||||
@@ -605,7 +605,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
.keycode = keycode,
|
||||
.scancode = scancode,
|
||||
.repeat = event->repeat,
|
||||
.mods_state = sc_mods_state_from_sdl(event->keysym.mod),
|
||||
.mods_state = sc_mods_state_from_sdl(event->mod),
|
||||
};
|
||||
|
||||
assert(im->kp->ops->process_key);
|
||||
@@ -674,7 +674,9 @@ sc_input_manager_process_touch(struct sc_input_manager *im,
|
||||
|
||||
int dw;
|
||||
int dh;
|
||||
SDL_GL_GetDrawableSize(im->screen->window, &dw, &dh);
|
||||
bool always_ok = SDL_GetWindowSizeInPixels(im->screen->window, &dw, &dh);
|
||||
(void) always_ok;
|
||||
assert(always_ok);
|
||||
|
||||
// SDL touch event coordinates are normalized in the range [0; 1]
|
||||
int32_t x = event->x * dw;
|
||||
@@ -687,7 +689,7 @@ sc_input_manager_process_touch(struct sc_input_manager *im,
|
||||
sc_screen_convert_drawable_to_frame_coords(im->screen, x, y),
|
||||
},
|
||||
.action = sc_touch_action_from_sdl(event->type),
|
||||
.pointer_id = event->fingerId,
|
||||
.pointer_id = event->fingerID,
|
||||
.pressure = event->pressure,
|
||||
};
|
||||
|
||||
@@ -723,7 +725,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
||||
|
||||
bool control = im->controller;
|
||||
bool paused = im->screen->paused;
|
||||
bool down = event->type == SDL_MOUSEBUTTONDOWN;
|
||||
bool down = event->type == SDL_EVENT_MOUSE_BUTTON_DOWN;
|
||||
|
||||
enum sc_mouse_button button = sc_mouse_button_from_sdl(event->button);
|
||||
if (button == SC_MOUSE_BUTTON_UNKNOWN) {
|
||||
@@ -736,8 +738,8 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
||||
}
|
||||
|
||||
SDL_Keymod keymod = SDL_GetModState();
|
||||
bool ctrl_pressed = keymod & KMOD_CTRL;
|
||||
bool shift_pressed = keymod & KMOD_SHIFT;
|
||||
bool ctrl_pressed = keymod & SDL_KMOD_CTRL;
|
||||
bool shift_pressed = keymod & SDL_KMOD_SHIFT;
|
||||
|
||||
if (control && !paused) {
|
||||
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
|
||||
@@ -889,20 +891,15 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
|
||||
}
|
||||
|
||||
// mouse_x and mouse_y are expressed in pixels relative to the window
|
||||
int mouse_x;
|
||||
int mouse_y;
|
||||
float mouse_x;
|
||||
float mouse_y;
|
||||
uint32_t buttons = SDL_GetMouseState(&mouse_x, &mouse_y);
|
||||
(void) buttons; // Actual buttons are tracked manually to ignore shortcuts
|
||||
|
||||
struct sc_mouse_scroll_event evt = {
|
||||
.position = sc_input_manager_get_position(im, mouse_x, mouse_y),
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 18)
|
||||
.hscroll = CLAMP(event->preciseX, -1.0f, 1.0f),
|
||||
.vscroll = CLAMP(event->preciseY, -1.0f, 1.0f),
|
||||
#else
|
||||
.hscroll = CLAMP(event->x, -1, 1),
|
||||
.vscroll = CLAMP(event->y, -1, 1),
|
||||
#endif
|
||||
.hscroll = event->x,
|
||||
.vscroll = event->y,
|
||||
.buttons_state = im->mouse_buttons_state,
|
||||
};
|
||||
|
||||
@@ -911,31 +908,31 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
|
||||
|
||||
static void
|
||||
sc_input_manager_process_gamepad_device(struct sc_input_manager *im,
|
||||
const SDL_ControllerDeviceEvent *event) {
|
||||
if (event->type == SDL_CONTROLLERDEVICEADDED) {
|
||||
SDL_GameController *gc = SDL_GameControllerOpen(event->which);
|
||||
const SDL_GamepadDeviceEvent *event) {
|
||||
if (event->type == SDL_EVENT_GAMEPAD_ADDED) {
|
||||
SDL_Gamepad *gc = SDL_OpenGamepad(event->which);
|
||||
if (!gc) {
|
||||
LOGW("Could not open game controller");
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_Joystick *joystick = SDL_GameControllerGetJoystick(gc);
|
||||
SDL_Joystick *joystick = SDL_GetGamepadJoystick(gc);
|
||||
if (!joystick) {
|
||||
LOGW("Could not get controller joystick");
|
||||
SDL_GameControllerClose(gc);
|
||||
SDL_CloseGamepad(gc);
|
||||
return;
|
||||
}
|
||||
|
||||
struct sc_gamepad_device_event evt = {
|
||||
.gamepad_id = SDL_JoystickInstanceID(joystick),
|
||||
.gamepad_id = SDL_GetJoystickID(joystick),
|
||||
};
|
||||
im->gp->ops->process_gamepad_added(im->gp, &evt);
|
||||
} else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
|
||||
} else if (event->type == SDL_EVENT_GAMEPAD_REMOVED) {
|
||||
SDL_JoystickID id = event->which;
|
||||
|
||||
SDL_GameController *gc = SDL_GameControllerFromInstanceID(id);
|
||||
SDL_Gamepad *gc = SDL_GetGamepadFromID(id);
|
||||
if (gc) {
|
||||
SDL_GameControllerClose(gc);
|
||||
SDL_CloseGamepad(gc);
|
||||
} else {
|
||||
LOGW("Unknown gamepad device removed");
|
||||
}
|
||||
@@ -952,7 +949,7 @@ sc_input_manager_process_gamepad_device(struct sc_input_manager *im,
|
||||
|
||||
static void
|
||||
sc_input_manager_process_gamepad_axis(struct sc_input_manager *im,
|
||||
const SDL_ControllerAxisEvent *event) {
|
||||
const SDL_GamepadAxisEvent *event) {
|
||||
enum sc_gamepad_axis axis = sc_gamepad_axis_from_sdl(event->axis);
|
||||
if (axis == SC_GAMEPAD_AXIS_UNKNOWN) {
|
||||
return;
|
||||
@@ -968,7 +965,7 @@ sc_input_manager_process_gamepad_axis(struct sc_input_manager *im,
|
||||
|
||||
static void
|
||||
sc_input_manager_process_gamepad_button(struct sc_input_manager *im,
|
||||
const SDL_ControllerButtonEvent *event) {
|
||||
const SDL_GamepadButtonEvent *event) {
|
||||
enum sc_gamepad_button button = sc_gamepad_button_from_sdl(event->button);
|
||||
if (button == SC_GAMEPAD_BUTTON_UNKNOWN) {
|
||||
return;
|
||||
@@ -991,8 +988,8 @@ is_apk(const char *file) {
|
||||
static void
|
||||
sc_input_manager_process_file(struct sc_input_manager *im,
|
||||
const SDL_DropEvent *event) {
|
||||
char *file = strdup(event->file);
|
||||
SDL_free(event->file);
|
||||
assert(event->type == SDL_EVENT_DROP_FILE);
|
||||
char *file = strdup(event->data);
|
||||
if (!file) {
|
||||
LOG_OOM();
|
||||
return;
|
||||
@@ -1016,66 +1013,66 @@ sc_input_manager_handle_event(struct sc_input_manager *im,
|
||||
bool control = im->controller;
|
||||
bool paused = im->screen->paused;
|
||||
switch (event->type) {
|
||||
case SDL_TEXTINPUT:
|
||||
case SDL_EVENT_TEXT_INPUT:
|
||||
if (!im->kp || paused) {
|
||||
break;
|
||||
}
|
||||
sc_input_manager_process_text_input(im, &event->text);
|
||||
break;
|
||||
case SDL_KEYDOWN:
|
||||
case SDL_KEYUP:
|
||||
case SDL_EVENT_KEY_DOWN:
|
||||
case SDL_EVENT_KEY_UP:
|
||||
// some key events do not interact with the device, so process the
|
||||
// event even if control is disabled
|
||||
sc_input_manager_process_key(im, &event->key);
|
||||
break;
|
||||
case SDL_MOUSEMOTION:
|
||||
case SDL_EVENT_MOUSE_MOTION:
|
||||
if (!im->mp || paused) {
|
||||
break;
|
||||
}
|
||||
sc_input_manager_process_mouse_motion(im, &event->motion);
|
||||
break;
|
||||
case SDL_MOUSEWHEEL:
|
||||
case SDL_EVENT_MOUSE_WHEEL:
|
||||
if (!im->mp || paused) {
|
||||
break;
|
||||
}
|
||||
sc_input_manager_process_mouse_wheel(im, &event->wheel);
|
||||
break;
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||
case SDL_EVENT_MOUSE_BUTTON_UP:
|
||||
// some mouse events do not interact with the device, so process
|
||||
// the event even if control is disabled
|
||||
sc_input_manager_process_mouse_button(im, &event->button);
|
||||
break;
|
||||
case SDL_FINGERMOTION:
|
||||
case SDL_FINGERDOWN:
|
||||
case SDL_FINGERUP:
|
||||
case SDL_EVENT_FINGER_MOTION:
|
||||
case SDL_EVENT_FINGER_DOWN:
|
||||
case SDL_EVENT_FINGER_UP:
|
||||
if (!im->mp || paused) {
|
||||
break;
|
||||
}
|
||||
sc_input_manager_process_touch(im, &event->tfinger);
|
||||
break;
|
||||
case SDL_CONTROLLERDEVICEADDED:
|
||||
case SDL_CONTROLLERDEVICEREMOVED:
|
||||
case SDL_EVENT_GAMEPAD_ADDED:
|
||||
case SDL_EVENT_GAMEPAD_REMOVED:
|
||||
// Handle device added or removed even if paused
|
||||
if (!im->gp) {
|
||||
break;
|
||||
}
|
||||
sc_input_manager_process_gamepad_device(im, &event->cdevice);
|
||||
sc_input_manager_process_gamepad_device(im, &event->gdevice);
|
||||
break;
|
||||
case SDL_CONTROLLERAXISMOTION:
|
||||
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
|
||||
if (!im->gp || paused) {
|
||||
break;
|
||||
}
|
||||
sc_input_manager_process_gamepad_axis(im, &event->caxis);
|
||||
sc_input_manager_process_gamepad_axis(im, &event->gaxis);
|
||||
break;
|
||||
case SDL_CONTROLLERBUTTONDOWN:
|
||||
case SDL_CONTROLLERBUTTONUP:
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_UP:
|
||||
if (!im->gp || paused) {
|
||||
break;
|
||||
}
|
||||
sc_input_manager_process_gamepad_button(im, &event->cbutton);
|
||||
sc_input_manager_process_gamepad_button(im, &event->gbutton);
|
||||
break;
|
||||
case SDL_DROPFILE: {
|
||||
case SDL_EVENT_DROP_FILE: {
|
||||
if (!control) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <SDL2/SDL_events.h>
|
||||
#include <SDL2/SDL_keycode.h>
|
||||
#include <SDL3/SDL_events.h>
|
||||
#include <SDL3/SDL_keycode.h>
|
||||
|
||||
#include "controller.h"
|
||||
#include "file_pusher.h"
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#ifdef HAVE_V4L2
|
||||
# include <libavdevice/avdevice.h>
|
||||
#endif
|
||||
#define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem
|
||||
#include <SDL2/SDL.h>
|
||||
#define SDL_FUNCTION_POINTER_IS_VOID_POINTER
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "cli.h"
|
||||
#include "options.h"
|
||||
|
||||
@@ -20,14 +20,11 @@ bool
|
||||
sc_mouse_capture_handle_event(struct sc_mouse_capture *mc,
|
||||
const SDL_Event *event) {
|
||||
switch (event->type) {
|
||||
case SDL_WINDOWEVENT:
|
||||
if (event->window.event == SDL_WINDOWEVENT_FOCUS_LOST) {
|
||||
sc_mouse_capture_set_active(mc, false);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case SDL_KEYDOWN: {
|
||||
SDL_Keycode key = event->key.keysym.sym;
|
||||
case SDL_EVENT_WINDOW_FOCUS_LOST:
|
||||
sc_mouse_capture_set_active(mc, false);
|
||||
return true;
|
||||
case SDL_EVENT_KEY_DOWN: {
|
||||
SDL_Keycode key = event->key.key;
|
||||
if (sc_mouse_capture_is_capture_key(mc, key)) {
|
||||
if (!mc->mouse_capture_key_pressed) {
|
||||
mc->mouse_capture_key_pressed = key;
|
||||
@@ -41,8 +38,8 @@ sc_mouse_capture_handle_event(struct sc_mouse_capture *mc,
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SDL_KEYUP: {
|
||||
SDL_Keycode key = event->key.keysym.sym;
|
||||
case SDL_EVENT_KEY_UP: {
|
||||
SDL_Keycode key = event->key.key;
|
||||
SDL_Keycode cap = mc->mouse_capture_key_pressed;
|
||||
mc->mouse_capture_key_pressed = 0;
|
||||
if (sc_mouse_capture_is_capture_key(mc, key)) {
|
||||
@@ -56,24 +53,24 @@ sc_mouse_capture_handle_event(struct sc_mouse_capture *mc,
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SDL_MOUSEWHEEL:
|
||||
case SDL_MOUSEMOTION:
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
case SDL_EVENT_MOUSE_WHEEL:
|
||||
case SDL_EVENT_MOUSE_MOTION:
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||
if (!sc_mouse_capture_is_active(mc)) {
|
||||
// The mouse will be captured on SDL_MOUSEBUTTONUP, so consume
|
||||
// the event
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
case SDL_EVENT_MOUSE_BUTTON_UP:
|
||||
if (!sc_mouse_capture_is_active(mc)) {
|
||||
sc_mouse_capture_set_active(mc, true);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case SDL_FINGERMOTION:
|
||||
case SDL_FINGERDOWN:
|
||||
case SDL_FINGERUP:
|
||||
case SDL_EVENT_FINGER_MOTION:
|
||||
case SDL_EVENT_FINGER_DOWN:
|
||||
case SDL_EVENT_FINGER_UP:
|
||||
// Touch events are not compatible with relative mode
|
||||
// (coordinates are not relative), so consume the event
|
||||
return true;
|
||||
@@ -84,27 +81,8 @@ sc_mouse_capture_handle_event(struct sc_mouse_capture *mc,
|
||||
|
||||
void
|
||||
sc_mouse_capture_set_active(struct sc_mouse_capture *mc, bool capture) {
|
||||
#ifdef __APPLE__
|
||||
// Workaround for SDL bug on macOS:
|
||||
// <https://github.com/libsdl-org/SDL/issues/5340>
|
||||
if (capture) {
|
||||
int mouse_x, mouse_y;
|
||||
SDL_GetGlobalMouseState(&mouse_x, &mouse_y);
|
||||
|
||||
int x, y, w, h;
|
||||
SDL_GetWindowPosition(mc->window, &x, &y);
|
||||
SDL_GetWindowSize(mc->window, &w, &h);
|
||||
|
||||
bool outside_window = mouse_x < x || mouse_x >= x + w
|
||||
|| mouse_y < y || mouse_y >= y + h;
|
||||
if (outside_window) {
|
||||
SDL_WarpMouseInWindow(mc->window, w / 2, h / 2);
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void) mc;
|
||||
#endif
|
||||
if (SDL_SetRelativeMouseMode(capture)) {
|
||||
bool ok = SDL_SetWindowRelativeMouseMode(mc->window, capture);
|
||||
if (!ok) {
|
||||
LOGE("Could not set relative mouse mode to %s: %s",
|
||||
capture ? "true" : "false", SDL_GetError());
|
||||
}
|
||||
@@ -112,8 +90,7 @@ sc_mouse_capture_set_active(struct sc_mouse_capture *mc, bool capture) {
|
||||
|
||||
bool
|
||||
sc_mouse_capture_is_active(struct sc_mouse_capture *mc) {
|
||||
(void) mc;
|
||||
return SDL_GetRelativeMouseMode();
|
||||
return SDL_GetWindowRelativeMouseMode(mc->window);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
struct sc_mouse_capture {
|
||||
SDL_Window *window;
|
||||
|
||||
@@ -3,21 +3,29 @@
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
void
|
||||
sc_opengl_init(struct sc_opengl *gl) {
|
||||
gl->GetString = SDL_GL_GetProcAddress("glGetString");
|
||||
gl->GetString = (const GLubyte *(*)(GLenum))
|
||||
SDL_GL_GetProcAddress("glGetString");
|
||||
assert(gl->GetString);
|
||||
|
||||
gl->TexParameterf = SDL_GL_GetProcAddress("glTexParameterf");
|
||||
gl->BindTexture = (void (*)(GLenum, GLuint))
|
||||
SDL_GL_GetProcAddress("glBindTexture");
|
||||
assert(gl->BindTexture);
|
||||
|
||||
gl->TexParameterf = (void (*)(GLenum, GLenum, GLfloat))
|
||||
SDL_GL_GetProcAddress("glTexParameterf");
|
||||
assert(gl->TexParameterf);
|
||||
|
||||
gl->TexParameteri = SDL_GL_GetProcAddress("glTexParameteri");
|
||||
gl->TexParameteri = (void (*)(GLenum, GLenum, GLint))
|
||||
SDL_GL_GetProcAddress("glTexParameteri");
|
||||
assert(gl->TexParameteri);
|
||||
|
||||
// optional
|
||||
gl->GenerateMipmap = SDL_GL_GetProcAddress("glGenerateMipmap");
|
||||
gl->GenerateMipmap = (void (*)(GLenum))
|
||||
SDL_GL_GetProcAddress("glGenerateMipmap");
|
||||
|
||||
const char *version = (const char *) gl->GetString(GL_VERSION);
|
||||
assert(version);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <SDL2/SDL_opengl.h>
|
||||
#include <SDL3/SDL_opengl.h>
|
||||
|
||||
struct sc_opengl {
|
||||
const char *version;
|
||||
@@ -15,6 +15,9 @@ struct sc_opengl {
|
||||
const GLubyte *
|
||||
(*GetString)(GLenum name);
|
||||
|
||||
void
|
||||
(*BindTexture)(GLenum target, GLuint texture);
|
||||
|
||||
void
|
||||
(*TexParameterf)(GLenum target, GLenum pname, GLfloat param);
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ const struct scrcpy_options scrcpy_options_default = {
|
||||
.capture_orientation_lock = SC_ORIENTATION_UNLOCKED,
|
||||
.display_orientation = SC_ORIENTATION_0,
|
||||
.record_orientation = SC_ORIENTATION_0,
|
||||
.display_ime_policy = SC_DISPLAY_IME_POLICY_UNDEFINED,
|
||||
.window_x = SC_WINDOW_POSITION_UNDEFINED,
|
||||
.window_y = SC_WINDOW_POSITION_UNDEFINED,
|
||||
.window_width = 0,
|
||||
|
||||
@@ -59,6 +59,14 @@ enum sc_audio_source {
|
||||
SC_AUDIO_SOURCE_OUTPUT,
|
||||
SC_AUDIO_SOURCE_MIC,
|
||||
SC_AUDIO_SOURCE_PLAYBACK,
|
||||
SC_AUDIO_SOURCE_MIC_UNPROCESSED,
|
||||
SC_AUDIO_SOURCE_MIC_CAMCORDER,
|
||||
SC_AUDIO_SOURCE_MIC_VOICE_RECOGNITION,
|
||||
SC_AUDIO_SOURCE_MIC_VOICE_COMMUNICATION,
|
||||
SC_AUDIO_SOURCE_VOICE_CALL,
|
||||
SC_AUDIO_SOURCE_VOICE_CALL_UPLINK,
|
||||
SC_AUDIO_SOURCE_VOICE_CALL_DOWNLINK,
|
||||
SC_AUDIO_SOURCE_VOICE_PERFORMANCE,
|
||||
};
|
||||
|
||||
enum sc_camera_facing {
|
||||
@@ -89,6 +97,13 @@ enum sc_orientation_lock {
|
||||
SC_ORIENTATION_LOCKED_INITIAL, // lock to initial device orientation
|
||||
};
|
||||
|
||||
enum sc_display_ime_policy {
|
||||
SC_DISPLAY_IME_POLICY_UNDEFINED,
|
||||
SC_DISPLAY_IME_POLICY_LOCAL,
|
||||
SC_DISPLAY_IME_POLICY_FALLBACK,
|
||||
SC_DISPLAY_IME_POLICY_HIDE,
|
||||
};
|
||||
|
||||
static inline bool
|
||||
sc_orientation_is_mirror(enum sc_orientation orientation) {
|
||||
assert(!(orientation & ~7));
|
||||
@@ -251,6 +266,7 @@ struct scrcpy_options {
|
||||
enum sc_orientation_lock capture_orientation_lock;
|
||||
enum sc_orientation display_orientation;
|
||||
enum sc_orientation record_orientation;
|
||||
enum sc_display_ime_policy display_ime_policy;
|
||||
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
|
||||
int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto"
|
||||
uint16_t window_width;
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <SDL2/SDL_clipboard.h>
|
||||
#include <stdlib.h>
|
||||
#include <SDL3/SDL_clipboard.h>
|
||||
|
||||
#include "device_msg.h"
|
||||
#include "events.h"
|
||||
@@ -53,8 +54,12 @@ task_set_clipboard(void *userdata) {
|
||||
if (same) {
|
||||
LOGD("Computer clipboard unchanged");
|
||||
} else {
|
||||
LOGI("Device clipboard copied");
|
||||
SDL_SetClipboardText(text);
|
||||
bool ok = SDL_SetClipboardText(text);
|
||||
if (ok) {
|
||||
LOGI("Device clipboard copied");
|
||||
} else {
|
||||
LOGE("Could not set clipboard: %s", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
free(text);
|
||||
|
||||
@@ -541,7 +541,10 @@ sc_recorder_set_orientation(AVStream *stream, enum sc_orientation orientation) {
|
||||
|
||||
static bool
|
||||
sc_recorder_video_packet_sink_open(struct sc_packet_sink *sink,
|
||||
AVCodecContext *ctx) {
|
||||
AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session) {
|
||||
(void) session;
|
||||
|
||||
struct sc_recorder *recorder = DOWNCAST_VIDEO(sink);
|
||||
// only written from this thread, no need to lock
|
||||
assert(!recorder->video_init);
|
||||
@@ -635,7 +638,10 @@ sc_recorder_video_packet_sink_push(struct sc_packet_sink *sink,
|
||||
|
||||
static bool
|
||||
sc_recorder_audio_packet_sink_open(struct sc_packet_sink *sink,
|
||||
AVCodecContext *ctx) {
|
||||
AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session) {
|
||||
(void) session;
|
||||
|
||||
struct sc_recorder *recorder = DOWNCAST_AUDIO(sink);
|
||||
assert(recorder->audio);
|
||||
// only written from this thread, no need to lock
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
// not needed here, but winsock2.h must never be included AFTER windows.h
|
||||
@@ -93,8 +93,8 @@ struct scrcpy {
|
||||
|
||||
#ifdef _WIN32
|
||||
static BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) {
|
||||
if (ctrl_type == CTRL_C_EVENT) {
|
||||
sc_push_event(SDL_QUIT);
|
||||
if (ctrl_type == CTRL_C_EVENT || ctrl_type == CTRL_BREAK_EVENT) {
|
||||
sc_push_event(SDL_EVENT_QUIT);
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
@@ -107,9 +107,9 @@ sdl_set_hints(const char *render_driver) {
|
||||
LOGW("Could not set render driver");
|
||||
}
|
||||
|
||||
// Linear filtering
|
||||
if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1")) {
|
||||
LOGW("Could not enable linear filtering");
|
||||
// App name used in various contexts (such as PulseAudio)
|
||||
if (!SDL_SetHint(SDL_HINT_APP_NAME, "scrcpy")) {
|
||||
LOGW("Could not set app name");
|
||||
}
|
||||
|
||||
// Handle a click to gain focus as any other click
|
||||
@@ -117,21 +117,17 @@ sdl_set_hints(const char *render_driver) {
|
||||
LOGW("Could not enable mouse focus clickthrough");
|
||||
}
|
||||
|
||||
#ifdef SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS
|
||||
// Disable synthetic mouse events from touch events
|
||||
// Touch events with id SDL_TOUCH_MOUSEID are ignored anyway, but it is
|
||||
// better not to generate them in the first place.
|
||||
if (!SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0")) {
|
||||
LOGW("Could not disable synthetic mouse events");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
|
||||
// Disable compositor bypassing on X11
|
||||
if (!SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0")) {
|
||||
LOGW("Could not disable X11 compositor bypass");
|
||||
}
|
||||
#endif
|
||||
|
||||
// Do not minimize on focus loss
|
||||
if (!SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0")) {
|
||||
@@ -158,14 +154,20 @@ sdl_configure(bool video_playback, bool disable_screensaver) {
|
||||
}
|
||||
|
||||
if (disable_screensaver) {
|
||||
SDL_DisableScreenSaver();
|
||||
bool ok = SDL_DisableScreenSaver();
|
||||
if (!ok) {
|
||||
LOGW("Could not disable screen saver");
|
||||
}
|
||||
} else {
|
||||
SDL_EnableScreenSaver();
|
||||
bool ok = SDL_EnableScreenSaver();
|
||||
if (!ok) {
|
||||
LOGW("Could not enable screen saver");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static enum scrcpy_exit_code
|
||||
event_loop(struct scrcpy *s) {
|
||||
event_loop(struct scrcpy *s, bool has_screen) {
|
||||
SDL_Event event;
|
||||
while (SDL_WaitEvent(&event)) {
|
||||
switch (event.type) {
|
||||
@@ -187,7 +189,7 @@ event_loop(struct scrcpy *s) {
|
||||
case SC_EVENT_TIME_LIMIT_REACHED:
|
||||
LOGI("Time limit reached");
|
||||
return SCRCPY_EXIT_SUCCESS;
|
||||
case SDL_QUIT:
|
||||
case SDL_EVENT_QUIT:
|
||||
LOGD("User requested to quit");
|
||||
return SCRCPY_EXIT_SUCCESS;
|
||||
case SC_EVENT_RUN_ON_MAIN_THREAD: {
|
||||
@@ -197,7 +199,7 @@ event_loop(struct scrcpy *s) {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (!sc_screen_handle_event(&s->screen, &event)) {
|
||||
if (has_screen && !sc_screen_handle_event(&s->screen, &event)) {
|
||||
return SCRCPY_EXIT_FAILURE;
|
||||
}
|
||||
break;
|
||||
@@ -227,7 +229,7 @@ await_for_server(bool *connected) {
|
||||
SDL_Event event;
|
||||
while (SDL_WaitEvent(&event)) {
|
||||
switch (event.type) {
|
||||
case SDL_QUIT:
|
||||
case SDL_EVENT_QUIT:
|
||||
if (connected) {
|
||||
*connected = false;
|
||||
}
|
||||
@@ -355,12 +357,19 @@ static void
|
||||
init_sdl_gamepads(void) {
|
||||
// Trigger a SDL_CONTROLLERDEVICEADDED event for all gamepads already
|
||||
// connected
|
||||
int num_joysticks = SDL_NumJoysticks();
|
||||
for (int i = 0; i < num_joysticks; ++i) {
|
||||
if (SDL_IsGameController(i)) {
|
||||
int count;
|
||||
SDL_JoystickID *joysticks = SDL_GetJoysticks(&count);
|
||||
if (!joysticks) {
|
||||
LOGE("Could not list joysticks: %s", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; ++i) {
|
||||
SDL_JoystickID joystick = joysticks[i];
|
||||
if (SDL_IsGamepad(joystick)) {
|
||||
SDL_Event event;
|
||||
event.cdevice.type = SDL_CONTROLLERDEVICEADDED;
|
||||
event.cdevice.which = i;
|
||||
event.gdevice.type = SDL_EVENT_GAMEPAD_ADDED;
|
||||
event.gdevice.which = i;
|
||||
SDL_PushEvent(&event);
|
||||
}
|
||||
}
|
||||
@@ -376,7 +385,7 @@ scrcpy(struct scrcpy_options *options) {
|
||||
struct scrcpy *s = &scrcpy;
|
||||
|
||||
// Minimal SDL initialization
|
||||
if (SDL_Init(SDL_INIT_EVENTS)) {
|
||||
if (!SDL_Init(SDL_INIT_EVENTS)) {
|
||||
LOGE("Could not initialize SDL: %s", SDL_GetError());
|
||||
return SCRCPY_EXIT_FAILURE;
|
||||
}
|
||||
@@ -436,6 +445,7 @@ scrcpy(struct scrcpy_options *options) {
|
||||
.control = options->control,
|
||||
.display_id = options->display_id,
|
||||
.new_display = options->new_display,
|
||||
.display_ime_policy = options->display_ime_policy,
|
||||
.video = options->video,
|
||||
.audio = options->audio,
|
||||
.audio_dup = options->audio_dup,
|
||||
@@ -501,7 +511,7 @@ scrcpy(struct scrcpy_options *options) {
|
||||
// --no-video-playback is passed so that clipboard synchronization
|
||||
// still works.
|
||||
// <https://github.com/Genymobile/scrcpy/issues/4418>
|
||||
if (SDL_Init(SDL_INIT_VIDEO)) {
|
||||
if (!SDL_Init(SDL_INIT_VIDEO)) {
|
||||
// If it fails, it is an error only if video playback is enabled
|
||||
if (options->video_playback) {
|
||||
LOGE("Could not initialize SDL video: %s", SDL_GetError());
|
||||
@@ -513,14 +523,14 @@ scrcpy(struct scrcpy_options *options) {
|
||||
}
|
||||
|
||||
if (options->audio_playback) {
|
||||
if (SDL_Init(SDL_INIT_AUDIO)) {
|
||||
if (!SDL_Init(SDL_INIT_AUDIO)) {
|
||||
LOGE("Could not initialize SDL audio: %s", SDL_GetError());
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
if (options->gamepad_input_mode != SC_GAMEPAD_INPUT_MODE_DISABLED) {
|
||||
if (SDL_Init(SDL_INIT_GAMECONTROLLER)) {
|
||||
if (!SDL_Init(SDL_INIT_GAMEPAD)) {
|
||||
LOGE("Could not initialize SDL gamepad: %s", SDL_GetError());
|
||||
goto end;
|
||||
}
|
||||
@@ -932,7 +942,7 @@ aoa_complete:
|
||||
}
|
||||
}
|
||||
|
||||
ret = event_loop(s);
|
||||
ret = event_loop(s, options->window);
|
||||
terminate_event_loop();
|
||||
LOGD("quit...");
|
||||
|
||||
|
||||
288
app/src/screen.c
288
app/src/screen.c
@@ -2,12 +2,13 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "events.h"
|
||||
#include "icon.h"
|
||||
#include "options.h"
|
||||
#include "util/log.h"
|
||||
#include "util/window.h"
|
||||
|
||||
#define DISPLAY_MARGINS 96
|
||||
|
||||
@@ -31,7 +32,9 @@ static struct sc_size
|
||||
get_window_size(const struct sc_screen *screen) {
|
||||
int width;
|
||||
int height;
|
||||
SDL_GetWindowSize(screen->window, &width, &height);
|
||||
bool always_ok = SDL_GetWindowSize(screen->window, &width, &height);
|
||||
(void) always_ok;
|
||||
assert(always_ok);
|
||||
|
||||
struct sc_size size;
|
||||
size.width = width;
|
||||
@@ -43,7 +46,9 @@ static struct sc_point
|
||||
get_window_position(const struct sc_screen *screen) {
|
||||
int x;
|
||||
int y;
|
||||
SDL_GetWindowPosition(screen->window, &x, &y);
|
||||
bool always_ok = SDL_GetWindowPosition(screen->window, &x, &y);
|
||||
(void) always_ok;
|
||||
assert(always_ok);
|
||||
|
||||
struct sc_point point;
|
||||
point.x = x;
|
||||
@@ -57,14 +62,19 @@ set_window_size(struct sc_screen *screen, struct sc_size new_size) {
|
||||
assert(!screen->fullscreen);
|
||||
assert(!screen->maximized);
|
||||
assert(!screen->minimized);
|
||||
SDL_SetWindowSize(screen->window, new_size.width, new_size.height);
|
||||
bool always_ok =
|
||||
SDL_SetWindowSize(screen->window, new_size.width, new_size.height);
|
||||
(void) always_ok;
|
||||
assert(always_ok);
|
||||
}
|
||||
|
||||
// get the preferred display bounds (i.e. the screen bounds with some margins)
|
||||
static bool
|
||||
get_preferred_display_bounds(struct sc_size *bounds) {
|
||||
SDL_Rect rect;
|
||||
if (SDL_GetDisplayUsableBounds(0, &rect)) {
|
||||
SDL_DisplayID display = SDL_GetPrimaryDisplay();
|
||||
bool ok = SDL_GetDisplayUsableBounds(display, &rect);
|
||||
if (!ok) {
|
||||
LOGW("Could not get display usable bounds: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
@@ -168,7 +178,9 @@ sc_screen_update_content_rect(struct sc_screen *screen) {
|
||||
|
||||
int dw;
|
||||
int dh;
|
||||
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
|
||||
bool always_ok = SDL_GetWindowSizeInPixels(screen->window, &dw, &dh);
|
||||
(void) always_ok;
|
||||
assert(always_ok);
|
||||
|
||||
struct sc_size content_size = screen->content_size;
|
||||
// The drawable size is the window size * the HiDPI scale
|
||||
@@ -208,6 +220,7 @@ sc_screen_update_content_rect(struct sc_screen *screen) {
|
||||
static void
|
||||
sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
|
||||
assert(screen->video);
|
||||
assert(screen->has_video_window);
|
||||
|
||||
if (update_content_rect) {
|
||||
sc_screen_update_content_rect(screen);
|
||||
@@ -225,7 +238,7 @@ sc_screen_render_novideo(struct sc_screen *screen) {
|
||||
(void) res; // any error already logged
|
||||
}
|
||||
|
||||
#if defined(__APPLE__) || defined(__WINDOWS__)
|
||||
#if defined(__APPLE__) || defined(_WIN32)
|
||||
# define CONTINUOUS_RESIZING_WORKAROUND
|
||||
#endif
|
||||
|
||||
@@ -235,28 +248,31 @@ sc_screen_render_novideo(struct sc_screen *screen) {
|
||||
//
|
||||
// <https://bugzilla.libsdl.org/show_bug.cgi?id=2077>
|
||||
// <https://stackoverflow.com/a/40693139/1987178>
|
||||
static int
|
||||
static bool
|
||||
event_watcher(void *data, SDL_Event *event) {
|
||||
struct sc_screen *screen = data;
|
||||
assert(screen->video);
|
||||
|
||||
if (event->type == SDL_WINDOWEVENT
|
||||
&& event->window.event == SDL_WINDOWEVENT_RESIZED) {
|
||||
if (event->type == SDL_EVENT_WINDOW_EXPOSED) {
|
||||
// In practice, it seems to always be called from the same thread in
|
||||
// that specific case. Anyway, it's just a workaround.
|
||||
sc_screen_render(screen, true);
|
||||
}
|
||||
return 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool
|
||||
sc_screen_frame_sink_open(struct sc_frame_sink *sink,
|
||||
const AVCodecContext *ctx) {
|
||||
const AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session) {
|
||||
assert(ctx->pix_fmt == AV_PIX_FMT_YUV420P);
|
||||
(void) ctx;
|
||||
(void) session;
|
||||
|
||||
struct sc_screen *screen = DOWNCAST(sink);
|
||||
(void) screen;
|
||||
|
||||
if (ctx->width <= 0 || ctx->width > 0xFFFF
|
||||
|| ctx->height <= 0 || ctx->height > 0xFFFF) {
|
||||
@@ -264,19 +280,6 @@ sc_screen_frame_sink_open(struct sc_frame_sink *sink,
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(ctx->width > 0 && ctx->width <= 0xFFFF);
|
||||
assert(ctx->height > 0 && ctx->height <= 0xFFFF);
|
||||
// screen->frame_size is never used before the event is pushed, and the
|
||||
// event acts as a memory barrier so it is safe without mutex
|
||||
screen->frame_size.width = ctx->width;
|
||||
screen->frame_size.height = ctx->height;
|
||||
|
||||
// Post the event on the UI thread (the texture must be created from there)
|
||||
bool ok = sc_push_event(SC_EVENT_SCREEN_INIT_SIZE);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
screen->open = true;
|
||||
#endif
|
||||
@@ -327,6 +330,7 @@ sc_screen_init(struct sc_screen *screen,
|
||||
const struct sc_screen_params *params) {
|
||||
screen->resize_pending = false;
|
||||
screen->has_frame = false;
|
||||
screen->has_video_window = false;
|
||||
screen->fullscreen = false;
|
||||
screen->maximized = false;
|
||||
screen->minimized = false;
|
||||
@@ -360,7 +364,7 @@ sc_screen_init(struct sc_screen *screen,
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t window_flags = SDL_WINDOW_ALLOW_HIGHDPI;
|
||||
uint32_t window_flags = SDL_WINDOW_HIGH_PIXEL_DENSITY;
|
||||
if (params->always_on_top) {
|
||||
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
|
||||
}
|
||||
@@ -394,22 +398,31 @@ sc_screen_init(struct sc_screen *screen,
|
||||
}
|
||||
|
||||
// The window will be positioned and sized on first video frame
|
||||
screen->window = SDL_CreateWindow(title, x, y, width, height, window_flags);
|
||||
screen->window =
|
||||
sc_create_sdl_window(title, x, y, width, height, window_flags);
|
||||
if (!screen->window) {
|
||||
LOGE("Could not create window: %s", SDL_GetError());
|
||||
goto error_destroy_fps_counter;
|
||||
}
|
||||
|
||||
ok = SDL_StartTextInput(screen->window);
|
||||
if (!ok) {
|
||||
LOGE("Could not enable text input: %s", SDL_GetError());
|
||||
goto error_destroy_window;
|
||||
}
|
||||
|
||||
SDL_Surface *icon = scrcpy_icon_load();
|
||||
if (icon) {
|
||||
SDL_SetWindowIcon(screen->window, icon);
|
||||
if (!SDL_SetWindowIcon(screen->window, icon)) {
|
||||
LOGW("Could not set window icon: %s", SDL_GetError());
|
||||
}
|
||||
} else if (params->video) {
|
||||
// just a warning
|
||||
LOGW("Could not load icon");
|
||||
} else {
|
||||
// without video, the icon is used as window content, it must be present
|
||||
LOGE("Could not load icon");
|
||||
goto error_destroy_fps_counter;
|
||||
goto error_destroy_window;
|
||||
}
|
||||
|
||||
SDL_Surface *icon_novideo = params->video ? NULL : icon;
|
||||
@@ -449,7 +462,11 @@ sc_screen_init(struct sc_screen *screen,
|
||||
|
||||
#ifdef CONTINUOUS_RESIZING_WORKAROUND
|
||||
if (screen->video) {
|
||||
SDL_AddEventWatch(event_watcher, screen);
|
||||
ok = SDL_AddEventWatch(event_watcher, screen);
|
||||
if (!ok) {
|
||||
LOGW("Could not add event watcher for continuous resizing: %s",
|
||||
SDL_GetError());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -496,7 +513,12 @@ sc_screen_show_initial_window(struct sc_screen *screen) {
|
||||
screen->req.height);
|
||||
|
||||
set_window_size(screen, window_size);
|
||||
SDL_SetWindowPosition(screen->window, x, y);
|
||||
|
||||
bool always_ok;
|
||||
(void) always_ok;
|
||||
|
||||
always_ok = SDL_SetWindowPosition(screen->window, x, y);
|
||||
assert(always_ok);
|
||||
|
||||
if (screen->req.fullscreen) {
|
||||
sc_screen_toggle_fullscreen(screen);
|
||||
@@ -506,13 +528,17 @@ sc_screen_show_initial_window(struct sc_screen *screen) {
|
||||
sc_fps_counter_start(&screen->fps_counter);
|
||||
}
|
||||
|
||||
SDL_ShowWindow(screen->window);
|
||||
always_ok = SDL_ShowWindow(screen->window);
|
||||
assert(always_ok);
|
||||
|
||||
sc_screen_update_content_rect(screen);
|
||||
}
|
||||
|
||||
void
|
||||
sc_screen_hide_window(struct sc_screen *screen) {
|
||||
SDL_HideWindow(screen->window);
|
||||
bool always_ok = SDL_HideWindow(screen->window);
|
||||
(void) always_ok;
|
||||
assert(always_ok);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -603,44 +629,6 @@ sc_screen_set_orientation(struct sc_screen *screen,
|
||||
sc_screen_render(screen, true);
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_screen_init_size(struct sc_screen *screen) {
|
||||
// Before first frame
|
||||
assert(!screen->has_frame);
|
||||
|
||||
// The requested size is passed via screen->frame_size
|
||||
|
||||
struct sc_size content_size =
|
||||
get_oriented_size(screen->frame_size, screen->orientation);
|
||||
screen->content_size = content_size;
|
||||
|
||||
enum sc_display_result res =
|
||||
sc_display_set_texture_size(&screen->display, screen->frame_size);
|
||||
return res != SC_DISPLAY_RESULT_ERROR;
|
||||
}
|
||||
|
||||
// recreate the texture and resize the window if the frame size has changed
|
||||
static enum sc_display_result
|
||||
prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
|
||||
assert(screen->video);
|
||||
|
||||
if (screen->frame_size.width == new_frame_size.width
|
||||
&& screen->frame_size.height == new_frame_size.height) {
|
||||
return SC_DISPLAY_RESULT_OK;
|
||||
}
|
||||
|
||||
// frame dimension changed
|
||||
screen->frame_size = new_frame_size;
|
||||
|
||||
struct sc_size new_content_size =
|
||||
get_oriented_size(new_frame_size, screen->orientation);
|
||||
set_content_size(screen, new_content_size);
|
||||
|
||||
sc_screen_update_content_rect(screen);
|
||||
|
||||
return sc_display_set_texture_size(&screen->display, screen->frame_size);
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_screen_apply_frame(struct sc_screen *screen) {
|
||||
assert(screen->video);
|
||||
@@ -649,7 +637,38 @@ sc_screen_apply_frame(struct sc_screen *screen) {
|
||||
|
||||
AVFrame *frame = screen->frame;
|
||||
struct sc_size new_frame_size = {frame->width, frame->height};
|
||||
enum sc_display_result res = prepare_for_frame(screen, new_frame_size);
|
||||
|
||||
if (!screen->has_frame
|
||||
|| screen->frame_size.width != new_frame_size.width
|
||||
|| screen->frame_size.height != new_frame_size.height) {
|
||||
// frame dimension changed
|
||||
screen->frame_size = new_frame_size;
|
||||
|
||||
struct sc_size new_content_size =
|
||||
get_oriented_size(new_frame_size, screen->orientation);
|
||||
if (screen->has_frame) {
|
||||
set_content_size(screen, new_content_size);
|
||||
sc_screen_update_content_rect(screen);
|
||||
} else {
|
||||
// This is the first frame
|
||||
screen->has_frame = true;
|
||||
screen->content_size = new_content_size;
|
||||
}
|
||||
|
||||
enum sc_display_result res =
|
||||
sc_display_prepare_texture(&screen->display, screen->frame_size,
|
||||
frame->colorspace, frame->color_range);
|
||||
if (res == SC_DISPLAY_RESULT_ERROR) {
|
||||
return false;
|
||||
}
|
||||
if (res == SC_DISPLAY_RESULT_PENDING) {
|
||||
// Not an error, but do not continue
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
enum sc_display_result res =
|
||||
sc_display_update_texture(&screen->display, frame);
|
||||
if (res == SC_DISPLAY_RESULT_ERROR) {
|
||||
return false;
|
||||
}
|
||||
@@ -658,17 +677,9 @@ sc_screen_apply_frame(struct sc_screen *screen) {
|
||||
return true;
|
||||
}
|
||||
|
||||
res = sc_display_update_texture(&screen->display, frame);
|
||||
if (res == SC_DISPLAY_RESULT_ERROR) {
|
||||
return false;
|
||||
}
|
||||
if (res == SC_DISPLAY_RESULT_PENDING) {
|
||||
// Not an error, but do not continue
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!screen->has_frame) {
|
||||
screen->has_frame = true;
|
||||
assert(screen->has_frame);
|
||||
if (!screen->has_video_window) {
|
||||
screen->has_video_window = true;
|
||||
// this is the very first frame, show the window
|
||||
sc_screen_show_initial_window(screen);
|
||||
|
||||
@@ -738,8 +749,8 @@ void
|
||||
sc_screen_toggle_fullscreen(struct sc_screen *screen) {
|
||||
assert(screen->video);
|
||||
|
||||
uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||
if (SDL_SetWindowFullscreen(screen->window, new_mode)) {
|
||||
bool ok = SDL_SetWindowFullscreen(screen->window, !screen->fullscreen);
|
||||
if (!ok) {
|
||||
LOGW("Could not switch fullscreen mode: %s", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
@@ -773,8 +784,16 @@ sc_screen_resize_to_fit(struct sc_screen *screen) {
|
||||
uint32_t new_x = point.x + (window_size.width - optimal_size.width) / 2;
|
||||
uint32_t new_y = point.y + (window_size.height - optimal_size.height) / 2;
|
||||
|
||||
SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height);
|
||||
SDL_SetWindowPosition(screen->window, new_x, new_y);
|
||||
bool always_ok;
|
||||
(void) always_ok;
|
||||
|
||||
always_ok = SDL_SetWindowSize(screen->window, optimal_size.width,
|
||||
optimal_size.height);
|
||||
assert(always_ok);
|
||||
|
||||
always_ok = SDL_SetWindowPosition(screen->window, new_x, new_y);
|
||||
assert(always_ok);
|
||||
|
||||
LOGD("Resized to optimal size: %ux%u", optimal_size.width,
|
||||
optimal_size.height);
|
||||
}
|
||||
@@ -787,29 +806,29 @@ sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool always_ok;
|
||||
(void) always_ok;
|
||||
|
||||
if (screen->maximized) {
|
||||
SDL_RestoreWindow(screen->window);
|
||||
always_ok = SDL_RestoreWindow(screen->window);
|
||||
assert(always_ok);
|
||||
screen->maximized = false;
|
||||
}
|
||||
|
||||
struct sc_size content_size = screen->content_size;
|
||||
SDL_SetWindowSize(screen->window, content_size.width, content_size.height);
|
||||
always_ok = SDL_SetWindowSize(screen->window, content_size.width,
|
||||
content_size.height);
|
||||
assert(always_ok);
|
||||
|
||||
LOGD("Resized to pixel-perfect: %ux%u", content_size.width,
|
||||
content_size.height);
|
||||
}
|
||||
|
||||
bool
|
||||
sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
|
||||
// !video implies !has_video_window
|
||||
assert(screen->video || !screen->has_video_window);
|
||||
switch (event->type) {
|
||||
case SC_EVENT_SCREEN_INIT_SIZE: {
|
||||
// The initial size is passed via screen->frame_size
|
||||
bool ok = sc_screen_init_size(screen);
|
||||
if (!ok) {
|
||||
LOGE("Could not initialize screen size");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case SC_EVENT_NEW_FRAME: {
|
||||
bool ok = sc_screen_update_frame(screen);
|
||||
if (!ok) {
|
||||
@@ -818,45 +837,38 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case SDL_WINDOWEVENT:
|
||||
if (!screen->video
|
||||
&& event->window.event == SDL_WINDOWEVENT_EXPOSED) {
|
||||
case SDL_EVENT_WINDOW_EXPOSED:
|
||||
if (!screen->video) {
|
||||
sc_screen_render_novideo(screen);
|
||||
} else if (screen->has_video_window) {
|
||||
sc_screen_render(screen, true);
|
||||
}
|
||||
|
||||
// !video implies !has_frame
|
||||
assert(screen->video || !screen->has_frame);
|
||||
if (!screen->has_frame) {
|
||||
// Do nothing
|
||||
return true;
|
||||
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
|
||||
if (screen->has_video_window) {
|
||||
sc_screen_render(screen, true);
|
||||
}
|
||||
return true;
|
||||
case SDL_EVENT_WINDOW_MAXIMIZED:
|
||||
screen->maximized = true;
|
||||
return true;
|
||||
case SDL_EVENT_WINDOW_MINIMIZED:
|
||||
screen->minimized = true;
|
||||
return true;
|
||||
case SDL_EVENT_WINDOW_RESTORED:
|
||||
if (screen->fullscreen) {
|
||||
// On Windows, in maximized+fullscreen, disabling
|
||||
// fullscreen mode unexpectedly triggers the "restored"
|
||||
// then "maximized" events, leaving the window in a
|
||||
// weird state (maximized according to the events, but
|
||||
// not maximized visually).
|
||||
return true;
|
||||
}
|
||||
switch (event->window.event) {
|
||||
case SDL_WINDOWEVENT_EXPOSED:
|
||||
sc_screen_render(screen, true);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||
sc_screen_render(screen, true);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_MAXIMIZED:
|
||||
screen->maximized = true;
|
||||
break;
|
||||
case SDL_WINDOWEVENT_MINIMIZED:
|
||||
screen->minimized = true;
|
||||
break;
|
||||
case SDL_WINDOWEVENT_RESTORED:
|
||||
if (screen->fullscreen) {
|
||||
// On Windows, in maximized+fullscreen, disabling
|
||||
// fullscreen mode unexpectedly triggers the "restored"
|
||||
// then "maximized" events, leaving the window in a
|
||||
// weird state (maximized according to the events, but
|
||||
// not maximized visually).
|
||||
break;
|
||||
}
|
||||
screen->maximized = false;
|
||||
screen->minimized = false;
|
||||
apply_pending_resize(screen);
|
||||
sc_screen_render(screen, true);
|
||||
break;
|
||||
screen->maximized = false;
|
||||
screen->minimized = false;
|
||||
if (screen->has_video_window) {
|
||||
apply_pending_resize(screen);
|
||||
sc_screen_render(screen, true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -936,10 +948,16 @@ sc_screen_convert_window_to_frame_coords(struct sc_screen *screen,
|
||||
|
||||
void
|
||||
sc_screen_hidpi_scale_coords(struct sc_screen *screen, int32_t *x, int32_t *y) {
|
||||
bool always_ok;
|
||||
(void) always_ok;
|
||||
|
||||
// take the HiDPI scaling (dw/ww and dh/wh) into account
|
||||
int ww, wh, dw, dh;
|
||||
SDL_GetWindowSize(screen->window, &ww, &wh);
|
||||
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
|
||||
always_ok = SDL_GetWindowSize(screen->window, &ww, &wh);
|
||||
assert(always_ok);
|
||||
|
||||
always_ok = SDL_GetWindowSizeInPixels(screen->window, &dw, &dh);
|
||||
assert(always_ok);
|
||||
|
||||
// scale for HiDPI (64 bits for intermediate multiplications)
|
||||
*x = (int64_t) *x * dw / ww;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL3/SDL.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavutil/frame.h>
|
||||
#include <libavutil/pixfmt.h>
|
||||
@@ -61,6 +61,7 @@ struct sc_screen {
|
||||
// rectangle of the content (excluding black borders)
|
||||
struct SDL_Rect rect;
|
||||
bool has_frame;
|
||||
bool has_video_window;
|
||||
bool fullscreen;
|
||||
bool maximized;
|
||||
bool minimized;
|
||||
|
||||
@@ -149,12 +149,43 @@ sc_server_get_audio_source_name(enum sc_audio_source audio_source) {
|
||||
return "mic";
|
||||
case SC_AUDIO_SOURCE_PLAYBACK:
|
||||
return "playback";
|
||||
case SC_AUDIO_SOURCE_MIC_UNPROCESSED:
|
||||
return "mic-unprocessed";
|
||||
case SC_AUDIO_SOURCE_MIC_CAMCORDER:
|
||||
return "mic-camcorder";
|
||||
case SC_AUDIO_SOURCE_MIC_VOICE_RECOGNITION:
|
||||
return "mic-voice-recognition";
|
||||
case SC_AUDIO_SOURCE_MIC_VOICE_COMMUNICATION:
|
||||
return "mic-voice-communication";
|
||||
case SC_AUDIO_SOURCE_VOICE_CALL:
|
||||
return "voice-call";
|
||||
case SC_AUDIO_SOURCE_VOICE_CALL_UPLINK:
|
||||
return "voice-call-uplink";
|
||||
case SC_AUDIO_SOURCE_VOICE_CALL_DOWNLINK:
|
||||
return "voice-call-downlink";
|
||||
case SC_AUDIO_SOURCE_VOICE_PERFORMANCE:
|
||||
return "voice-performance";
|
||||
default:
|
||||
assert(!"unexpected audio source");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static const char *
|
||||
sc_server_get_display_ime_policy_name(enum sc_display_ime_policy policy) {
|
||||
switch (policy) {
|
||||
case SC_DISPLAY_IME_POLICY_LOCAL:
|
||||
return "local";
|
||||
case SC_DISPLAY_IME_POLICY_FALLBACK:
|
||||
return "fallback";
|
||||
case SC_DISPLAY_IME_POLICY_HIDE:
|
||||
return "hide";
|
||||
default:
|
||||
assert(!"unexpected display IME policy");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
validate_string(const char *s) {
|
||||
// The parameters values are passed as command line arguments to adb, so
|
||||
@@ -376,6 +407,10 @@ execute_server(struct sc_server *server,
|
||||
VALIDATE_STRING(params->new_display);
|
||||
ADD_PARAM("new_display=%s", params->new_display);
|
||||
}
|
||||
if (params->display_ime_policy != SC_DISPLAY_IME_POLICY_UNDEFINED) {
|
||||
ADD_PARAM("display_ime_policy=%s",
|
||||
sc_server_get_display_ime_policy_name(params->display_ime_policy));
|
||||
}
|
||||
if (!params->vd_destroy_content) {
|
||||
ADD_PARAM("vd_destroy_content=false");
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ struct sc_server_params {
|
||||
bool control;
|
||||
uint32_t display_id;
|
||||
const char *new_display;
|
||||
enum sc_display_ime_policy display_ime_policy;
|
||||
bool video;
|
||||
bool audio;
|
||||
bool audio_dup;
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <SDL2/SDL_keycode.h>
|
||||
#include <SDL3/SDL_keycode.h>
|
||||
|
||||
#include "options.h"
|
||||
|
||||
#define SC_SDL_SHORTCUT_MODS_MASK (KMOD_CTRL | KMOD_ALT | KMOD_GUI)
|
||||
#define SC_SDL_SHORTCUT_MODS_MASK (SDL_KMOD_CTRL | SDL_KMOD_ALT | SDL_KMOD_GUI)
|
||||
|
||||
// input: OR of enum sc_shortcut_mod
|
||||
// output: OR of SDL_Keymod
|
||||
@@ -18,22 +18,22 @@ static inline uint16_t
|
||||
sc_shortcut_mods_to_sdl(uint8_t shortcut_mods) {
|
||||
uint16_t sdl_mod = 0;
|
||||
if (shortcut_mods & SC_SHORTCUT_MOD_LCTRL) {
|
||||
sdl_mod |= KMOD_LCTRL;
|
||||
sdl_mod |= SDL_KMOD_LCTRL;
|
||||
}
|
||||
if (shortcut_mods & SC_SHORTCUT_MOD_RCTRL) {
|
||||
sdl_mod |= KMOD_RCTRL;
|
||||
sdl_mod |= SDL_KMOD_RCTRL;
|
||||
}
|
||||
if (shortcut_mods & SC_SHORTCUT_MOD_LALT) {
|
||||
sdl_mod |= KMOD_LALT;
|
||||
sdl_mod |= SDL_KMOD_LALT;
|
||||
}
|
||||
if (shortcut_mods & SC_SHORTCUT_MOD_RALT) {
|
||||
sdl_mod |= KMOD_RALT;
|
||||
sdl_mod |= SDL_KMOD_RALT;
|
||||
}
|
||||
if (shortcut_mods & SC_SHORTCUT_MOD_LSUPER) {
|
||||
sdl_mod |= KMOD_LGUI;
|
||||
sdl_mod |= SDL_KMOD_LGUI;
|
||||
}
|
||||
if (shortcut_mods & SC_SHORTCUT_MOD_RSUPER) {
|
||||
sdl_mod |= KMOD_RGUI;
|
||||
sdl_mod |= SDL_KMOD_RGUI;
|
||||
}
|
||||
return sdl_mod;
|
||||
}
|
||||
@@ -50,12 +50,12 @@ sc_shortcut_mods_is_shortcut_mod(uint16_t sdl_shortcut_mods, uint16_t sdl_mod) {
|
||||
static inline bool
|
||||
sc_shortcut_mods_is_shortcut_key(uint16_t sdl_shortcut_mods,
|
||||
SDL_Keycode keycode) {
|
||||
return (sdl_shortcut_mods & KMOD_LCTRL && keycode == SDLK_LCTRL)
|
||||
|| (sdl_shortcut_mods & KMOD_RCTRL && keycode == SDLK_RCTRL)
|
||||
|| (sdl_shortcut_mods & KMOD_LALT && keycode == SDLK_LALT)
|
||||
|| (sdl_shortcut_mods & KMOD_RALT && keycode == SDLK_RALT)
|
||||
|| (sdl_shortcut_mods & KMOD_LGUI && keycode == SDLK_LGUI)
|
||||
|| (sdl_shortcut_mods & KMOD_RGUI && keycode == SDLK_RGUI);
|
||||
return (sdl_shortcut_mods & SDL_KMOD_LCTRL && keycode == SDLK_LCTRL)
|
||||
|| (sdl_shortcut_mods & SDL_KMOD_RCTRL && keycode == SDLK_RCTRL)
|
||||
|| (sdl_shortcut_mods & SDL_KMOD_LALT && keycode == SDLK_LALT)
|
||||
|| (sdl_shortcut_mods & SDL_KMOD_RALT && keycode == SDLK_RALT)
|
||||
|| (sdl_shortcut_mods & SDL_KMOD_LGUI && keycode == SDLK_LGUI)
|
||||
|| (sdl_shortcut_mods & SDL_KMOD_RGUI && keycode == SDLK_RGUI);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include <stdbool.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
|
||||
#include "trait/packet_sink.h"
|
||||
|
||||
/**
|
||||
* Frame sink trait.
|
||||
*
|
||||
@@ -17,9 +19,16 @@ struct sc_frame_sink {
|
||||
|
||||
struct sc_frame_sink_ops {
|
||||
/* The codec context is valid until the sink is closed */
|
||||
bool (*open)(struct sc_frame_sink *sink, const AVCodecContext *ctx);
|
||||
bool (*open)(struct sc_frame_sink *sink, const AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session);
|
||||
void (*close)(struct sc_frame_sink *sink);
|
||||
bool (*push)(struct sc_frame_sink *sink, const AVFrame *frame);
|
||||
|
||||
/**
|
||||
* Optional callback to be notified of a new stream session.
|
||||
*/
|
||||
bool (*push_session)(struct sc_frame_sink *sink,
|
||||
const struct sc_stream_session *session);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -27,11 +27,12 @@ sc_frame_source_sinks_close_firsts(struct sc_frame_source *source,
|
||||
|
||||
bool
|
||||
sc_frame_source_sinks_open(struct sc_frame_source *source,
|
||||
const AVCodecContext *ctx) {
|
||||
const AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session) {
|
||||
assert(source->sink_count);
|
||||
for (unsigned i = 0; i < source->sink_count; ++i) {
|
||||
struct sc_frame_sink *sink = source->sinks[i];
|
||||
if (!sink->ops->open(sink, ctx)) {
|
||||
if (!sink->ops->open(sink, ctx, session)) {
|
||||
sc_frame_source_sinks_close_firsts(source, i);
|
||||
return false;
|
||||
}
|
||||
@@ -59,3 +60,18 @@ sc_frame_source_sinks_push(struct sc_frame_source *source,
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_frame_source_sinks_push_session(struct sc_frame_source *source,
|
||||
const struct sc_stream_session *session) {
|
||||
assert(source->sink_count);
|
||||
for (unsigned i = 0; i < source->sink_count; ++i) {
|
||||
struct sc_frame_sink *sink = source->sinks[i];
|
||||
if (sink->ops->push_session &&
|
||||
!sink->ops->push_session(sink, session)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,8 @@ sc_frame_source_add_sink(struct sc_frame_source *source,
|
||||
|
||||
bool
|
||||
sc_frame_source_sinks_open(struct sc_frame_source *source,
|
||||
const AVCodecContext *ctx);
|
||||
const AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session);
|
||||
|
||||
void
|
||||
sc_frame_source_sinks_close(struct sc_frame_source *source);
|
||||
@@ -37,4 +38,8 @@ bool
|
||||
sc_frame_source_sinks_push(struct sc_frame_source *source,
|
||||
const AVFrame *frame);
|
||||
|
||||
bool
|
||||
sc_frame_source_sinks_push_session(struct sc_frame_source *source,
|
||||
const struct sc_stream_session *session);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -15,12 +15,28 @@ struct sc_packet_sink {
|
||||
const struct sc_packet_sink_ops *ops;
|
||||
};
|
||||
|
||||
struct sc_stream_session_video {
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
};
|
||||
|
||||
struct sc_stream_session {
|
||||
struct sc_stream_session_video video;
|
||||
};
|
||||
|
||||
struct sc_packet_sink_ops {
|
||||
/* The codec context is valid until the sink is closed */
|
||||
bool (*open)(struct sc_packet_sink *sink, AVCodecContext *ctx);
|
||||
bool (*open)(struct sc_packet_sink *sink, AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session);
|
||||
void (*close)(struct sc_packet_sink *sink);
|
||||
bool (*push)(struct sc_packet_sink *sink, const AVPacket *packet);
|
||||
|
||||
/**
|
||||
* Optional callback to be notified of a new stream session.
|
||||
*/
|
||||
bool (*push_session)(struct sc_packet_sink *sink,
|
||||
const struct sc_stream_session *session);
|
||||
|
||||
/*/
|
||||
* Called when the input stream has been disabled at runtime.
|
||||
*
|
||||
|
||||
@@ -27,11 +27,12 @@ sc_packet_source_sinks_close_firsts(struct sc_packet_source *source,
|
||||
|
||||
bool
|
||||
sc_packet_source_sinks_open(struct sc_packet_source *source,
|
||||
AVCodecContext *ctx) {
|
||||
AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session) {
|
||||
assert(source->sink_count);
|
||||
for (unsigned i = 0; i < source->sink_count; ++i) {
|
||||
struct sc_packet_sink *sink = source->sinks[i];
|
||||
if (!sink->ops->open(sink, ctx)) {
|
||||
if (!sink->ops->open(sink, ctx, session)) {
|
||||
sc_packet_source_sinks_close_firsts(source, i);
|
||||
return false;
|
||||
}
|
||||
@@ -60,6 +61,20 @@ sc_packet_source_sinks_push(struct sc_packet_source *source,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_packet_source_sinks_push_session(struct sc_packet_source *source,
|
||||
const struct sc_stream_session *session) {
|
||||
assert(source->sink_count);
|
||||
for (unsigned i = 0; i < source->sink_count; ++i) {
|
||||
struct sc_packet_sink *sink = source->sinks[i];
|
||||
if (!sink->ops->push_session(sink, session)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
sc_packet_source_sinks_disable(struct sc_packet_source *source) {
|
||||
assert(source->sink_count);
|
||||
|
||||
@@ -28,7 +28,8 @@ sc_packet_source_add_sink(struct sc_packet_source *source,
|
||||
|
||||
bool
|
||||
sc_packet_source_sinks_open(struct sc_packet_source *source,
|
||||
AVCodecContext *ctx);
|
||||
AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session);
|
||||
|
||||
void
|
||||
sc_packet_source_sinks_close(struct sc_packet_source *source);
|
||||
@@ -37,6 +38,10 @@ bool
|
||||
sc_packet_source_sinks_push(struct sc_packet_source *source,
|
||||
const AVPacket *packet);
|
||||
|
||||
bool
|
||||
sc_packet_source_sinks_push_session(struct sc_packet_source *source,
|
||||
const struct sc_stream_session *session);
|
||||
|
||||
void
|
||||
sc_packet_source_sinks_disable(struct sc_packet_source *source);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include <SDL2/SDL_gamecontroller.h>
|
||||
#include <SDL3/SDL_gamepad.h>
|
||||
|
||||
#include "hid/hid_gamepad.h"
|
||||
#include "input_events.h"
|
||||
@@ -74,10 +74,10 @@ sc_gamepad_processor_process_gamepad_added(struct sc_gamepad_processor *gp,
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_GameController* game_controller =
|
||||
SDL_GameControllerFromInstanceID(event->gamepad_id);
|
||||
SDL_Gamepad * game_controller =
|
||||
SDL_GetGamepadFromID(event->gamepad_id);
|
||||
assert(game_controller);
|
||||
const char *name = SDL_GameControllerName(game_controller);
|
||||
const char *name = SDL_GetGamepadName(game_controller);
|
||||
LOGI("Gamepad added: [%" PRIu32 "] %s", event->gamepad_id, name);
|
||||
|
||||
sc_gamepad_uhid_send_open(gamepad, &hid_open);
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <SDL2/SDL_keyboard.h>
|
||||
#include <SDL2/SDL_keycode.h>
|
||||
#include <SDL3/SDL_keyboard.h>
|
||||
#include <SDL3/SDL_keycode.h>
|
||||
|
||||
#include "util/log.h"
|
||||
#include "util/thread.h"
|
||||
|
||||
@@ -55,7 +55,10 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
|
||||
struct sc_mouse_uhid *mouse = DOWNCAST(mp);
|
||||
|
||||
struct sc_hid_input hid_input;
|
||||
sc_hid_mouse_generate_input_from_scroll(&hid_input, event);
|
||||
if (!sc_hid_mouse_generate_input_from_scroll(&mouse->hid, &hid_input,
|
||||
event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
sc_mouse_uhid_send_input(mouse, &hid_input, "mouse scroll");
|
||||
}
|
||||
@@ -63,6 +66,8 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
|
||||
bool
|
||||
sc_mouse_uhid_init(struct sc_mouse_uhid *mouse,
|
||||
struct sc_controller *controller) {
|
||||
sc_hid_mouse_init(&mouse->hid);
|
||||
|
||||
mouse->controller = controller;
|
||||
|
||||
static const struct sc_mouse_processor_ops ops = {
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "controller.h"
|
||||
#include "hid/hid_mouse.h"
|
||||
#include "trait/mouse_processor.h"
|
||||
|
||||
struct sc_mouse_uhid {
|
||||
struct sc_mouse_processor mouse_processor; // mouse processor trait
|
||||
|
||||
struct sc_hid_mouse hid;
|
||||
struct sc_controller *controller;
|
||||
};
|
||||
|
||||
|
||||
@@ -42,7 +42,10 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
|
||||
struct sc_mouse_aoa *mouse = DOWNCAST(mp);
|
||||
|
||||
struct sc_hid_input hid_input;
|
||||
sc_hid_mouse_generate_input_from_scroll(&hid_input, event);
|
||||
if (!sc_hid_mouse_generate_input_from_scroll(&mouse->hid, &hid_input,
|
||||
event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sc_aoa_push_input(mouse->aoa, &hid_input)) {
|
||||
LOGW("Could not push AOA HID input (mouse scroll)");
|
||||
@@ -62,6 +65,8 @@ sc_mouse_aoa_init(struct sc_mouse_aoa *mouse, struct sc_aoa *aoa) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sc_hid_mouse_init(&mouse->hid);
|
||||
|
||||
static const struct sc_mouse_processor_ops ops = {
|
||||
.process_mouse_motion = sc_mouse_processor_process_mouse_motion,
|
||||
.process_mouse_click = sc_mouse_processor_process_mouse_click,
|
||||
|
||||
@@ -6,11 +6,13 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "usb/aoa_hid.h"
|
||||
#include "hid/hid_mouse.h"
|
||||
#include "trait/mouse_processor.h"
|
||||
|
||||
struct sc_mouse_aoa {
|
||||
struct sc_mouse_processor mouse_processor; // mouse processor trait
|
||||
|
||||
struct sc_hid_mouse hid;
|
||||
struct sc_aoa *aoa;
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
# include "adb/adb.h"
|
||||
@@ -45,7 +45,7 @@ event_loop(struct scrcpy_otg *s) {
|
||||
case SC_EVENT_AOA_OPEN_ERROR:
|
||||
LOGE("AOA open error");
|
||||
return SCRCPY_EXIT_FAILURE;
|
||||
case SDL_QUIT:
|
||||
case SDL_EVENT_QUIT:
|
||||
LOGD("User requested to quit");
|
||||
return SCRCPY_EXIT_SUCCESS;
|
||||
default:
|
||||
@@ -63,22 +63,18 @@ scrcpy_otg(struct scrcpy_options *options) {
|
||||
|
||||
const char *serial = options->serial;
|
||||
|
||||
if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1")) {
|
||||
LOGW("Could not enable linear filtering");
|
||||
}
|
||||
|
||||
if (!SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1")) {
|
||||
LOGW("Could not allow joystick background events");
|
||||
}
|
||||
|
||||
// Minimal SDL initialization
|
||||
if (SDL_Init(SDL_INIT_EVENTS)) {
|
||||
if (!SDL_Init(SDL_INIT_EVENTS)) {
|
||||
LOGE("Could not initialize SDL: %s", SDL_GetError());
|
||||
return SCRCPY_EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (options->gamepad_input_mode != SC_GAMEPAD_INPUT_MODE_DISABLED) {
|
||||
if (SDL_Init(SDL_INIT_GAMECONTROLLER)) {
|
||||
if (!SDL_Init(SDL_INIT_GAMEPAD)) {
|
||||
LOGE("Could not initialize SDL controller: %s", SDL_GetError());
|
||||
// Not fatal, keyboard/mouse should still work
|
||||
}
|
||||
|
||||
@@ -7,14 +7,24 @@
|
||||
#include "options.h"
|
||||
#include "util/acksync.h"
|
||||
#include "util/log.h"
|
||||
#include "util/window.h"
|
||||
|
||||
static void
|
||||
sc_screen_otg_render(struct sc_screen_otg *screen) {
|
||||
SDL_RenderClear(screen->renderer);
|
||||
bool always_ok = SDL_RenderClear(screen->renderer);
|
||||
(void) always_ok;
|
||||
assert(always_ok);
|
||||
|
||||
if (screen->texture) {
|
||||
SDL_RenderCopy(screen->renderer, screen->texture, NULL, NULL);
|
||||
bool ok =
|
||||
SDL_RenderTexture(screen->renderer, screen->texture, NULL, NULL);
|
||||
if (!ok) {
|
||||
LOGW("Could not render texture: %s", SDL_GetError());
|
||||
}
|
||||
}
|
||||
SDL_RenderPresent(screen->renderer);
|
||||
|
||||
always_ok = SDL_RenderPresent(screen->renderer);
|
||||
assert(always_ok);
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -34,7 +44,7 @@ sc_screen_otg_init(struct sc_screen_otg *screen,
|
||||
int width = params->window_width ? params->window_width : 256;
|
||||
int height = params->window_height ? params->window_height : 256;
|
||||
|
||||
uint32_t window_flags = SDL_WINDOW_ALLOW_HIGHDPI;
|
||||
uint32_t window_flags = SDL_WINDOW_HIGH_PIXEL_DENSITY;
|
||||
if (params->always_on_top) {
|
||||
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
|
||||
}
|
||||
@@ -42,13 +52,14 @@ sc_screen_otg_init(struct sc_screen_otg *screen,
|
||||
window_flags |= SDL_WINDOW_BORDERLESS;
|
||||
}
|
||||
|
||||
screen->window = SDL_CreateWindow(title, x, y, width, height, window_flags);
|
||||
screen->window =
|
||||
sc_create_sdl_window(title, x, y, width, height, window_flags);
|
||||
if (!screen->window) {
|
||||
LOGE("Could not create window: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
screen->renderer = SDL_CreateRenderer(screen->window, -1, 0);
|
||||
screen->renderer = SDL_CreateRenderer(screen->window, NULL);
|
||||
if (!screen->renderer) {
|
||||
LOGE("Could not create renderer: %s", SDL_GetError());
|
||||
goto error_destroy_window;
|
||||
@@ -57,9 +68,15 @@ sc_screen_otg_init(struct sc_screen_otg *screen,
|
||||
SDL_Surface *icon = scrcpy_icon_load();
|
||||
|
||||
if (icon) {
|
||||
SDL_SetWindowIcon(screen->window, icon);
|
||||
bool ok = SDL_SetWindowIcon(screen->window, icon);
|
||||
if (!ok) {
|
||||
LOGW("Could not set window icon: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
if (SDL_RenderSetLogicalSize(screen->renderer, icon->w, icon->h)) {
|
||||
ok = SDL_SetRenderLogicalPresentation(screen->renderer, icon->w,
|
||||
icon->h,
|
||||
SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
||||
if (!ok) {
|
||||
LOGW("Could not set renderer logical size: %s", SDL_GetError());
|
||||
// don't fail
|
||||
}
|
||||
@@ -108,10 +125,10 @@ sc_screen_otg_process_key(struct sc_screen_otg *screen,
|
||||
|
||||
struct sc_key_event evt = {
|
||||
.action = sc_action_from_sdl_keyboard_type(event->type),
|
||||
.keycode = sc_keycode_from_sdl(event->keysym.sym),
|
||||
.scancode = sc_scancode_from_sdl(event->keysym.scancode),
|
||||
.keycode = sc_keycode_from_sdl(event->key),
|
||||
.scancode = sc_scancode_from_sdl(event->scancode),
|
||||
.repeat = event->repeat,
|
||||
.mods_state = sc_mods_state_from_sdl(event->keysym.mod),
|
||||
.mods_state = sc_mods_state_from_sdl(event->mod),
|
||||
};
|
||||
|
||||
assert(kp->ops->process_key);
|
||||
@@ -175,34 +192,34 @@ sc_screen_otg_process_mouse_wheel(struct sc_screen_otg *screen,
|
||||
|
||||
static void
|
||||
sc_screen_otg_process_gamepad_device(struct sc_screen_otg *screen,
|
||||
const SDL_ControllerDeviceEvent *event) {
|
||||
const SDL_GamepadDeviceEvent *event) {
|
||||
assert(screen->gamepad);
|
||||
struct sc_gamepad_processor *gp = &screen->gamepad->gamepad_processor;
|
||||
|
||||
if (event->type == SDL_CONTROLLERDEVICEADDED) {
|
||||
SDL_GameController *gc = SDL_GameControllerOpen(event->which);
|
||||
if (event->type == SDL_EVENT_GAMEPAD_ADDED) {
|
||||
SDL_Gamepad *gc = SDL_OpenGamepad(event->which);
|
||||
if (!gc) {
|
||||
LOGW("Could not open game controller");
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_Joystick *joystick = SDL_GameControllerGetJoystick(gc);
|
||||
SDL_Joystick *joystick = SDL_GetGamepadJoystick(gc);
|
||||
if (!joystick) {
|
||||
LOGW("Could not get controller joystick");
|
||||
SDL_GameControllerClose(gc);
|
||||
SDL_CloseGamepad(gc);
|
||||
return;
|
||||
}
|
||||
|
||||
struct sc_gamepad_device_event evt = {
|
||||
.gamepad_id = SDL_JoystickInstanceID(joystick),
|
||||
.gamepad_id = SDL_GetJoystickID(joystick),
|
||||
};
|
||||
gp->ops->process_gamepad_added(gp, &evt);
|
||||
} else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
|
||||
} else if (event->type == SDL_EVENT_GAMEPAD_REMOVED) {
|
||||
SDL_JoystickID id = event->which;
|
||||
|
||||
SDL_GameController *gc = SDL_GameControllerFromInstanceID(id);
|
||||
SDL_Gamepad *gc = SDL_GetGamepadFromID(id);
|
||||
if (gc) {
|
||||
SDL_GameControllerClose(gc);
|
||||
SDL_CloseGamepad(gc);
|
||||
} else {
|
||||
LOGW("Unknown gamepad device removed");
|
||||
}
|
||||
@@ -216,7 +233,7 @@ sc_screen_otg_process_gamepad_device(struct sc_screen_otg *screen,
|
||||
|
||||
static void
|
||||
sc_screen_otg_process_gamepad_axis(struct sc_screen_otg *screen,
|
||||
const SDL_ControllerAxisEvent *event) {
|
||||
const SDL_GamepadAxisEvent *event) {
|
||||
assert(screen->gamepad);
|
||||
struct sc_gamepad_processor *gp = &screen->gamepad->gamepad_processor;
|
||||
|
||||
@@ -235,7 +252,7 @@ sc_screen_otg_process_gamepad_axis(struct sc_screen_otg *screen,
|
||||
|
||||
static void
|
||||
sc_screen_otg_process_gamepad_button(struct sc_screen_otg *screen,
|
||||
const SDL_ControllerButtonEvent *event) {
|
||||
const SDL_GamepadButtonEvent *event) {
|
||||
assert(screen->gamepad);
|
||||
struct sc_gamepad_processor *gp = &screen->gamepad->gamepad_processor;
|
||||
|
||||
@@ -260,59 +277,55 @@ sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event) {
|
||||
}
|
||||
|
||||
switch (event->type) {
|
||||
case SDL_WINDOWEVENT:
|
||||
switch (event->window.event) {
|
||||
case SDL_WINDOWEVENT_EXPOSED:
|
||||
sc_screen_otg_render(screen);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
case SDL_KEYDOWN:
|
||||
case SDL_EVENT_WINDOW_EXPOSED:
|
||||
sc_screen_otg_render(screen);
|
||||
break;
|
||||
case SDL_EVENT_KEY_DOWN:
|
||||
if (screen->keyboard) {
|
||||
sc_screen_otg_process_key(screen, &event->key);
|
||||
}
|
||||
break;
|
||||
case SDL_KEYUP:
|
||||
case SDL_EVENT_KEY_UP:
|
||||
if (screen->keyboard) {
|
||||
sc_screen_otg_process_key(screen, &event->key);
|
||||
}
|
||||
break;
|
||||
case SDL_MOUSEMOTION:
|
||||
case SDL_EVENT_MOUSE_MOTION:
|
||||
if (screen->mouse) {
|
||||
sc_screen_otg_process_mouse_motion(screen, &event->motion);
|
||||
}
|
||||
break;
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||
if (screen->mouse) {
|
||||
sc_screen_otg_process_mouse_button(screen, &event->button);
|
||||
}
|
||||
break;
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
case SDL_EVENT_MOUSE_BUTTON_UP:
|
||||
if (screen->mouse) {
|
||||
sc_screen_otg_process_mouse_button(screen, &event->button);
|
||||
}
|
||||
break;
|
||||
case SDL_MOUSEWHEEL:
|
||||
case SDL_EVENT_MOUSE_WHEEL:
|
||||
if (screen->mouse) {
|
||||
sc_screen_otg_process_mouse_wheel(screen, &event->wheel);
|
||||
}
|
||||
break;
|
||||
case SDL_CONTROLLERDEVICEADDED:
|
||||
case SDL_CONTROLLERDEVICEREMOVED:
|
||||
case SDL_EVENT_GAMEPAD_ADDED:
|
||||
case SDL_EVENT_GAMEPAD_REMOVED:
|
||||
// Handle device added or removed even if paused
|
||||
if (screen->gamepad) {
|
||||
sc_screen_otg_process_gamepad_device(screen, &event->cdevice);
|
||||
sc_screen_otg_process_gamepad_device(screen, &event->gdevice);
|
||||
}
|
||||
break;
|
||||
case SDL_CONTROLLERAXISMOTION:
|
||||
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
|
||||
if (screen->gamepad) {
|
||||
sc_screen_otg_process_gamepad_axis(screen, &event->caxis);
|
||||
sc_screen_otg_process_gamepad_axis(screen, &event->gaxis);
|
||||
}
|
||||
break;
|
||||
case SDL_CONTROLLERBUTTONDOWN:
|
||||
case SDL_CONTROLLERBUTTONUP:
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_UP:
|
||||
if (screen->gamepad) {
|
||||
sc_screen_otg_process_gamepad_button(screen, &event->cbutton);
|
||||
sc_screen_otg_process_gamepad_button(screen, &event->gbutton);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "mouse_capture.h"
|
||||
#include "usb/gamepad_aoa.h"
|
||||
|
||||
@@ -116,3 +116,38 @@ sc_audiobuf_write(struct sc_audiobuf *buf, const void *from_,
|
||||
|
||||
return samples_count;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
sc_audiobuf_write_silence(struct sc_audiobuf *buf, uint32_t samples_count) {
|
||||
// Only the writer thread can write head, so memory_order_relaxed is
|
||||
// sufficient
|
||||
uint32_t head = atomic_load_explicit(&buf->head, memory_order_relaxed);
|
||||
|
||||
// The tail cursor is updated after the data is consumed by the reader
|
||||
uint32_t tail = atomic_load_explicit(&buf->tail, memory_order_acquire);
|
||||
|
||||
uint32_t can_write = (buf->alloc_size + tail - head - 1) % buf->alloc_size;
|
||||
if (!can_write) {
|
||||
return 0;
|
||||
}
|
||||
if (samples_count > can_write) {
|
||||
samples_count = can_write;
|
||||
}
|
||||
|
||||
uint32_t right_count = buf->alloc_size - head;
|
||||
if (right_count > samples_count) {
|
||||
right_count = samples_count;
|
||||
}
|
||||
memset(buf->data + (head * buf->sample_size), 0,
|
||||
right_count * buf->sample_size);
|
||||
|
||||
if (samples_count > right_count) {
|
||||
uint32_t left_count = samples_count - right_count;
|
||||
memset(buf->data, 0, left_count * buf->sample_size);
|
||||
}
|
||||
|
||||
uint32_t new_head = (head + samples_count) % buf->alloc_size;
|
||||
atomic_store_explicit(&buf->head, new_head, memory_order_release);
|
||||
|
||||
return samples_count;
|
||||
}
|
||||
|
||||
@@ -50,6 +50,9 @@ uint32_t
|
||||
sc_audiobuf_write(struct sc_audiobuf *buf, const void *from,
|
||||
uint32_t samples_count);
|
||||
|
||||
uint32_t
|
||||
sc_audiobuf_write_silence(struct sc_audiobuf *buf, uint32_t samples);
|
||||
|
||||
static inline uint32_t
|
||||
sc_audiobuf_capacity(struct sc_audiobuf *buf) {
|
||||
assert(buf->alloc_size);
|
||||
|
||||
@@ -50,13 +50,13 @@ log_level_sdl_to_sc(SDL_LogPriority priority) {
|
||||
void
|
||||
sc_set_log_level(enum sc_log_level level) {
|
||||
SDL_LogPriority sdl_log = log_level_sc_to_sdl(level);
|
||||
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, sdl_log);
|
||||
SDL_LogSetPriority(SDL_LOG_CATEGORY_CUSTOM, sdl_log);
|
||||
SDL_SetLogPriority(SDL_LOG_CATEGORY_APPLICATION, sdl_log);
|
||||
SDL_SetLogPriority(SDL_LOG_CATEGORY_CUSTOM, sdl_log);
|
||||
}
|
||||
|
||||
enum sc_log_level
|
||||
sc_get_log_level(void) {
|
||||
SDL_LogPriority sdl_log = SDL_LogGetPriority(SDL_LOG_CATEGORY_APPLICATION);
|
||||
SDL_LogPriority sdl_log = SDL_GetLogPriority(SDL_LOG_CATEGORY_APPLICATION);
|
||||
return log_level_sdl_to_sc(sdl_log);
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ sc_av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
|
||||
free(local_fmt);
|
||||
}
|
||||
|
||||
static const char *const sc_sdl_log_priority_names[SDL_NUM_LOG_PRIORITIES] = {
|
||||
static const char *const sc_sdl_log_priority_names[SDL_LOG_PRIORITY_COUNT] = {
|
||||
[SDL_LOG_PRIORITY_VERBOSE] = "VERBOSE",
|
||||
[SDL_LOG_PRIORITY_DEBUG] = "DEBUG",
|
||||
[SDL_LOG_PRIORITY_INFO] = "INFO",
|
||||
@@ -144,14 +144,14 @@ sc_sdl_log_print(void *userdata, int category, SDL_LogPriority priority,
|
||||
(void) category;
|
||||
|
||||
FILE *out = priority < SDL_LOG_PRIORITY_WARN ? stdout : stderr;
|
||||
assert(priority < SDL_NUM_LOG_PRIORITIES);
|
||||
assert(priority < SDL_LOG_PRIORITY_COUNT);
|
||||
const char *prio_name = sc_sdl_log_priority_names[priority];
|
||||
fprintf(out, "%s: %s\n", prio_name, message);
|
||||
}
|
||||
|
||||
void
|
||||
sc_log_configure(void) {
|
||||
SDL_LogSetOutputFunction(sc_sdl_log_print, NULL);
|
||||
SDL_SetLogOutputFunction(sc_sdl_log_print, NULL);
|
||||
// Redirect FFmpeg logs to SDL logs
|
||||
av_log_set_callback(sc_av_log_callback);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <SDL2/SDL_log.h>
|
||||
#include <SDL3/SDL_log.h>
|
||||
|
||||
#include "options.h"
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <ws2tcpip.h>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <SDL2/SDL_thread.h>
|
||||
#include <SDL3/SDL_mutex.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
@@ -31,11 +31,7 @@ static SDL_ThreadPriority
|
||||
to_sdl_thread_priority(enum sc_thread_priority priority) {
|
||||
switch (priority) {
|
||||
case SC_THREAD_PRIORITY_TIME_CRITICAL:
|
||||
#ifdef SCRCPY_SDL_HAS_THREAD_PRIORITY_TIME_CRITICAL
|
||||
return SDL_THREAD_PRIORITY_TIME_CRITICAL;
|
||||
#else
|
||||
// fall through
|
||||
#endif
|
||||
case SC_THREAD_PRIORITY_HIGH:
|
||||
return SDL_THREAD_PRIORITY_HIGH;
|
||||
case SC_THREAD_PRIORITY_NORMAL:
|
||||
@@ -51,8 +47,8 @@ to_sdl_thread_priority(enum sc_thread_priority priority) {
|
||||
bool
|
||||
sc_thread_set_priority(enum sc_thread_priority priority) {
|
||||
SDL_ThreadPriority sdl_priority = to_sdl_thread_priority(priority);
|
||||
int r = SDL_SetThreadPriority(sdl_priority);
|
||||
if (r) {
|
||||
bool ok = SDL_SetCurrentThreadPriority(sdl_priority);
|
||||
if (!ok) {
|
||||
LOGD("Could not set thread priority: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
@@ -67,7 +63,7 @@ sc_thread_join(sc_thread *thread, int *status) {
|
||||
|
||||
bool
|
||||
sc_mutex_init(sc_mutex *mutex) {
|
||||
SDL_mutex *sdl_mutex = SDL_CreateMutex();
|
||||
SDL_Mutex *sdl_mutex = SDL_CreateMutex();
|
||||
if (!sdl_mutex) {
|
||||
LOG_OOM();
|
||||
return false;
|
||||
@@ -89,40 +85,25 @@ void
|
||||
sc_mutex_lock(sc_mutex *mutex) {
|
||||
// SDL mutexes are recursive, but we don't want to use recursive mutexes
|
||||
assert(!sc_mutex_held(mutex));
|
||||
int r = SDL_LockMutex(mutex->mutex);
|
||||
SDL_LockMutex(mutex->mutex);
|
||||
#ifndef NDEBUG
|
||||
if (r) {
|
||||
LOGE("Could not lock mutex: %s", SDL_GetError());
|
||||
abort();
|
||||
}
|
||||
|
||||
atomic_store_explicit(&mutex->locker, sc_thread_get_id(),
|
||||
memory_order_relaxed);
|
||||
#else
|
||||
(void) r;
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
sc_mutex_unlock(sc_mutex *mutex) {
|
||||
#ifndef NDEBUG
|
||||
assert(sc_mutex_held(mutex));
|
||||
#ifndef NDEBUG
|
||||
atomic_store_explicit(&mutex->locker, 0, memory_order_relaxed);
|
||||
#endif
|
||||
int r = SDL_UnlockMutex(mutex->mutex);
|
||||
#ifndef NDEBUG
|
||||
if (r) {
|
||||
LOGE("Could not lock mutex: %s", SDL_GetError());
|
||||
abort();
|
||||
}
|
||||
#else
|
||||
(void) r;
|
||||
#endif
|
||||
SDL_UnlockMutex(mutex->mutex);
|
||||
}
|
||||
|
||||
sc_thread_id
|
||||
sc_thread_get_id(void) {
|
||||
return SDL_ThreadID();
|
||||
return SDL_GetCurrentThreadID();
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
@@ -136,7 +117,7 @@ sc_mutex_held(struct sc_mutex *mutex) {
|
||||
|
||||
bool
|
||||
sc_cond_init(sc_cond *cond) {
|
||||
SDL_cond *sdl_cond = SDL_CreateCond();
|
||||
SDL_Condition *sdl_cond = SDL_CreateCondition();
|
||||
if (!sdl_cond) {
|
||||
LOG_OOM();
|
||||
return false;
|
||||
@@ -148,22 +129,15 @@ sc_cond_init(sc_cond *cond) {
|
||||
|
||||
void
|
||||
sc_cond_destroy(sc_cond *cond) {
|
||||
SDL_DestroyCond(cond->cond);
|
||||
SDL_DestroyCondition(cond->cond);
|
||||
}
|
||||
|
||||
void
|
||||
sc_cond_wait(sc_cond *cond, sc_mutex *mutex) {
|
||||
int r = SDL_CondWait(cond->cond, mutex->mutex);
|
||||
SDL_WaitCondition(cond->cond, mutex->mutex);
|
||||
#ifndef NDEBUG
|
||||
if (r) {
|
||||
LOGE("Could not wait on condition: %s", SDL_GetError());
|
||||
abort();
|
||||
}
|
||||
|
||||
atomic_store_explicit(&mutex->locker, sc_thread_get_id(),
|
||||
memory_order_relaxed);
|
||||
#else
|
||||
(void) r;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -177,44 +151,22 @@ sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, sc_tick deadline) {
|
||||
// Round up to the next millisecond to guarantee that the deadline is
|
||||
// reached when returning due to timeout
|
||||
uint32_t ms = SC_TICK_TO_MS(deadline - now + SC_TICK_FROM_MS(1) - 1);
|
||||
int r = SDL_CondWaitTimeout(cond->cond, mutex->mutex, ms);
|
||||
bool signaled = SDL_WaitConditionTimeout(cond->cond, mutex->mutex, ms);
|
||||
#ifndef NDEBUG
|
||||
if (r < 0) {
|
||||
LOGE("Could not wait on condition with timeout: %s", SDL_GetError());
|
||||
abort();
|
||||
}
|
||||
|
||||
atomic_store_explicit(&mutex->locker, sc_thread_get_id(),
|
||||
memory_order_relaxed);
|
||||
#endif
|
||||
assert(r == 0 || r == SDL_MUTEX_TIMEDOUT);
|
||||
// The deadline is reached on timeout
|
||||
assert(r != SDL_MUTEX_TIMEDOUT || sc_tick_now() >= deadline);
|
||||
return r == 0;
|
||||
assert(signaled || sc_tick_now() >= deadline);
|
||||
return signaled;
|
||||
}
|
||||
|
||||
void
|
||||
sc_cond_signal(sc_cond *cond) {
|
||||
int r = SDL_CondSignal(cond->cond);
|
||||
#ifndef NDEBUG
|
||||
if (r) {
|
||||
LOGE("Could not signal a condition: %s", SDL_GetError());
|
||||
abort();
|
||||
}
|
||||
#else
|
||||
(void) r;
|
||||
#endif
|
||||
SDL_SignalCondition(cond->cond);
|
||||
}
|
||||
|
||||
void
|
||||
sc_cond_broadcast(sc_cond *cond) {
|
||||
int r = SDL_CondBroadcast(cond->cond);
|
||||
#ifndef NDEBUG
|
||||
if (r) {
|
||||
LOGE("Could not broadcast a condition: %s", SDL_GetError());
|
||||
abort();
|
||||
}
|
||||
#else
|
||||
(void) r;
|
||||
#endif
|
||||
SDL_BroadcastCondition(cond->cond);
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
|
||||
/* Forward declarations */
|
||||
typedef struct SDL_Thread SDL_Thread;
|
||||
typedef struct SDL_mutex SDL_mutex;
|
||||
typedef struct SDL_cond SDL_cond;
|
||||
typedef struct SDL_Mutex SDL_Mutex;
|
||||
typedef struct SDL_Condition SDL_Condition;
|
||||
|
||||
typedef int sc_thread_fn(void *);
|
||||
typedef unsigned sc_thread_id;
|
||||
@@ -29,14 +29,14 @@ enum sc_thread_priority {
|
||||
};
|
||||
|
||||
typedef struct sc_mutex {
|
||||
SDL_mutex *mutex;
|
||||
SDL_Mutex *mutex;
|
||||
#ifndef NDEBUG
|
||||
sc_atomic_thread_id locker;
|
||||
#endif
|
||||
} sc_mutex;
|
||||
|
||||
typedef struct sc_cond {
|
||||
SDL_cond *cond;
|
||||
SDL_Condition *cond;
|
||||
} sc_cond;
|
||||
|
||||
extern sc_thread_id SC_MAIN_THREAD_ID;
|
||||
|
||||
@@ -191,7 +191,8 @@ sc_vecdeque_reallocdata_(void *ptr, size_t newcap, size_t item_size,
|
||||
|
||||
size_t right_len = MIN(size, oldcap - oldorigin);
|
||||
assert(right_len);
|
||||
memcpy(newptr, (char *) ptr + (oldorigin * item_size), right_len * item_size);
|
||||
memcpy(newptr, (char *) ptr + (oldorigin * item_size),
|
||||
right_len * item_size);
|
||||
|
||||
if (size > right_len) {
|
||||
memcpy((char *) newptr + (right_len * item_size), ptr,
|
||||
|
||||
33
app/src/util/window.c
Normal file
33
app/src/util/window.c
Normal file
@@ -0,0 +1,33 @@
|
||||
#include "window.h"
|
||||
|
||||
SDL_Window *
|
||||
sc_create_sdl_window(const char *title, int64_t x, int64_t y, int64_t width,
|
||||
int64_t height, int64_t flags) {
|
||||
SDL_Window *window = NULL;
|
||||
|
||||
SDL_PropertiesID props = SDL_CreateProperties();
|
||||
if (!props) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool ok =
|
||||
SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING,
|
||||
title);
|
||||
ok &= SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, x);
|
||||
ok &= SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, y);
|
||||
ok &= SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER,
|
||||
width);
|
||||
ok &= SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER,
|
||||
height);
|
||||
ok &= SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_FLAGS_NUMBER,
|
||||
flags);
|
||||
|
||||
if (!ok) {
|
||||
SDL_DestroyProperties(props);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
window = SDL_CreateWindowWithProperties(props);
|
||||
SDL_DestroyProperties(props);
|
||||
return window;
|
||||
}
|
||||
13
app/src/util/window.h
Normal file
13
app/src/util/window.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef SC_WINDOW_H
|
||||
#define SC_WINDOW_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <SDL3/SDL_video.h>
|
||||
|
||||
SDL_Window *
|
||||
sc_create_sdl_window(const char *title, int64_t x, int64_t y, int64_t width,
|
||||
int64_t height, int64_t flags);
|
||||
|
||||
#endif
|
||||
@@ -146,9 +146,11 @@ run_v4l2_sink(void *data) {
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_v4l2_sink_open(struct sc_v4l2_sink *vs, const AVCodecContext *ctx) {
|
||||
sc_v4l2_sink_open(struct sc_v4l2_sink *vs, const AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session) {
|
||||
assert(ctx->pix_fmt == AV_PIX_FMT_YUV420P);
|
||||
(void) ctx;
|
||||
(void) session;
|
||||
|
||||
bool ok = sc_frame_buffer_init(&vs->fb);
|
||||
if (!ok) {
|
||||
@@ -326,9 +328,10 @@ sc_v4l2_sink_push(struct sc_v4l2_sink *vs, const AVFrame *frame) {
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_v4l2_frame_sink_open(struct sc_frame_sink *sink, const AVCodecContext *ctx) {
|
||||
sc_v4l2_frame_sink_open(struct sc_frame_sink *sink, const AVCodecContext *ctx,
|
||||
const struct sc_stream_session *session) {
|
||||
struct sc_v4l2_sink *vs = DOWNCAST(sink);
|
||||
return sc_v4l2_sink_open(vs, ctx);
|
||||
return sc_v4l2_sink_open(vs, ctx, session);
|
||||
}
|
||||
|
||||
static void
|
||||
|
||||
@@ -10,17 +10,20 @@
|
||||
#ifdef HAVE_USB
|
||||
# include <libusb-1.0/libusb.h>
|
||||
#endif
|
||||
#include <SDL2/SDL_version.h>
|
||||
#include <SDL3/SDL_version.h>
|
||||
|
||||
void
|
||||
scrcpy_print_version(void) {
|
||||
printf("\nDependencies (compiled / linked):\n");
|
||||
|
||||
SDL_version sdl;
|
||||
SDL_GetVersion(&sdl);
|
||||
int sdl = SDL_GetVersion();
|
||||
printf(" - SDL: %u.%u.%u / %u.%u.%u\n",
|
||||
SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL,
|
||||
(unsigned) sdl.major, (unsigned) sdl.minor, (unsigned) sdl.patch);
|
||||
SDL_MAJOR_VERSION,
|
||||
SDL_MINOR_VERSION,
|
||||
SDL_MICRO_VERSION,
|
||||
SDL_VERSIONNUM_MAJOR(sdl),
|
||||
SDL_VERSIONNUM_MINOR(sdl),
|
||||
SDL_VERSIONNUM_MICRO(sdl));
|
||||
|
||||
unsigned avcodec = avcodec_version();
|
||||
printf(" - libavcodec: %u.%u.%u / %u.%u.%u\n",
|
||||
|
||||
@@ -113,6 +113,14 @@ static void test_audiobuf_partial_read_write(void) {
|
||||
uint32_t expected2[] = {4, 5, 6, 1, 2, 3, 4, 1, 2, 3};
|
||||
assert(!memcmp(data, expected2, 12));
|
||||
|
||||
w = sc_audiobuf_write_silence(&buf, 4);
|
||||
assert(w == 4);
|
||||
|
||||
r = sc_audiobuf_read(&buf, data, 4);
|
||||
assert(r == 4);
|
||||
uint32_t expected3[] = {0, 0, 0, 0};
|
||||
assert(!memcmp(data, expected3, 4));
|
||||
|
||||
sc_audiobuf_destroy(&buf);
|
||||
}
|
||||
|
||||
|
||||
@@ -127,8 +127,8 @@ static void test_serialize_inject_scroll_event(void) {
|
||||
.height = 1920,
|
||||
},
|
||||
},
|
||||
.hscroll = 1,
|
||||
.vscroll = -1,
|
||||
.hscroll = 16,
|
||||
.vscroll = -16,
|
||||
.buttons = 1,
|
||||
},
|
||||
};
|
||||
@@ -141,8 +141,8 @@ static void test_serialize_inject_scroll_event(void) {
|
||||
SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
|
||||
0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x04, 0x02, // 260 1026
|
||||
0x04, 0x38, 0x07, 0x80, // 1080 1920
|
||||
0x7F, 0xFF, // 1 (float encoded as i16)
|
||||
0x80, 0x00, // -1 (float encoded as i16)
|
||||
0x7F, 0xFF, // 16 (float encoded as i16 in the range [-16, 16])
|
||||
0x80, 0x00, // -16 (float encoded as i16 in the range [-16, 16])
|
||||
0x00, 0x00, 0x00, 0x01, // 1
|
||||
};
|
||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||
|
||||
14
doc/audio.md
14
doc/audio.md
@@ -66,6 +66,20 @@ the computer:
|
||||
scrcpy --audio-source=mic --no-video --no-playback --record=file.opus
|
||||
```
|
||||
|
||||
Many sources are available:
|
||||
|
||||
- `output` (default): forwards the whole audio output, and disables playback on the device (mapped to [`REMOTE_SUBMIX`](https://developer.android.com/reference/android/media/MediaRecorder.AudioSource#REMOTE_SUBMIX)).
|
||||
- `playback`: captures the audio playback (Android apps can opt-out, so the whole output is not necessarily captured).
|
||||
- `mic`: captures the microphone (mapped to [`MIC`](https://developer.android.com/reference/android/media/MediaRecorder.AudioSource#MIC)).
|
||||
- `mic-unprocessed`: captures the microphone unprocessed (raw) sound (mapped to [`UNPROCESSED`](https://developer.android.com/reference/android/media/MediaRecorder.AudioSource#UNPROCESSED)).
|
||||
- `mic-camcorder`: captures the microphone tuned for video recording, with the same orientation as the camera if available (mapped to [`CAMCORDER`](https://developer.android.com/reference/android/media/MediaRecorder.AudioSource#CAMCORDER)).
|
||||
- `mic-voice-recognition`: captures the microphone tuned for voice recognition (mapped to [`VOICE_RECOGNITION`](https://developer.android.com/reference/android/media/MediaRecorder.AudioSource#VOICE_RECOGNITION)).
|
||||
- `mic-voice-communication`: captures the microphone tuned for voice communications (it will for instance take advantage of echo cancellation or automatic gain control if available) (mapped to [`VOICE_COMMUNICATION`](https://developer.android.com/reference/android/media/MediaRecorder.AudioSource#VOICE_COMMUNICATION)).
|
||||
- `voice-call`: captures voice call (mapped to [`VOICE_CALL`](https://developer.android.com/reference/android/media/MediaRecorder.AudioSource#VOICE_CALL)).
|
||||
- `voice-call-uplink`: captures voice call uplink only (mapped to [`VOICE_UPLINK`](https://developer.android.com/reference/android/media/MediaRecorder.AudioSource#VOICE_UPLINK)).
|
||||
- `voice-call-downlink`: captures voice call downlink only (mapped to [`VOICE_DOWNLINK`](https://developer.android.com/reference/android/media/MediaRecorder.AudioSource#VOICE_DOWNLINK)).
|
||||
- `voice-performance`: captures audio meant to be processed for live performance (karaoke), includes both the microphone and the device playback (mapped to [`VOICE_PERFORMANCE`](https://developer.android.com/reference/android/media/MediaRecorder.AudioSource#VOICE_PERFORMANCE)).
|
||||
|
||||
### Duplication
|
||||
|
||||
An alternative device audio capture method is also available (only for Android
|
||||
|
||||
@@ -233,10 +233,10 @@ install` must be run as root)._
|
||||
|
||||
#### Option 2: Use prebuilt server
|
||||
|
||||
- [`scrcpy-server-v3.1`][direct-scrcpy-server]
|
||||
<sub>SHA-256: `958f0944a62f23b1f33a16e9eb14844c1a04b882ca175a738c16d23cb22b86c0`</sub>
|
||||
- [`scrcpy-server-v3.3.3`][direct-scrcpy-server]
|
||||
<sub>SHA-256: `7e70323ba7f259649dd4acce97ac4fefbae8102b2c6d91e2e7be613fd5354be0`</sub>
|
||||
|
||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-server-v3.1
|
||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-server-v3.3.3
|
||||
|
||||
Download the prebuilt server somewhere, and specify its path during the Meson
|
||||
configuration:
|
||||
|
||||
@@ -113,16 +113,17 @@ with the device IP address you found)_.
|
||||
7. Run `scrcpy` as usual.
|
||||
8. Run `adb disconnect` once you're done.
|
||||
|
||||
Since Android 11, a [wireless debugging option][adb-wireless] allows to bypass
|
||||
having to physically connect your device directly to your computer.
|
||||
Since Android 11, a [wireless debugging option][adb-wireless] allows you to
|
||||
bypass having to physically connect your device to your computer.
|
||||
|
||||
[adb-wireless]: https://developer.android.com/studio/command-line/adb#wireless-android11-command-line
|
||||
|
||||
|
||||
## Autostart
|
||||
|
||||
A small tool (by the scrcpy author) allows to run arbitrary commands whenever a
|
||||
new Android device is connected: [AutoAdb]. It can be used to start scrcpy:
|
||||
A small tool (by the scrcpy author) allows you to run arbitrary commands
|
||||
whenever a new Android device is connected: [AutoAdb]. It can be used to start
|
||||
scrcpy:
|
||||
|
||||
```bash
|
||||
autoadb scrcpy -s '{}'
|
||||
|
||||
@@ -409,12 +409,11 @@ with any client which uses the same protocol.
|
||||
|
||||
For simplicity, some [server-specific options] have been added to produce raw
|
||||
streams easily:
|
||||
- `send_device_meta=false`: disable the device metata (in practice, the device
|
||||
- `send_device_meta=false`: disable device metadata (in practice, the device
|
||||
name) sent on the _first_ socket
|
||||
- `send_frame_meta=false`: disable the 12-byte header for each packet
|
||||
- `send_dummy_byte`: disable the dummy byte sent on forward connections
|
||||
- `send_codec_meta`: disable the codec information (and initial device size for
|
||||
video)
|
||||
- `send_stream_meta`: disable codec and video size metadata
|
||||
- `raw_stream`: disable all the above
|
||||
|
||||
[server-specific options]: https://github.com/Genymobile/scrcpy/blob/a3cdf1a6b86ea22786e1f7d09b9c202feabc6949/server/src/main/java/com/genymobile/scrcpy/Options.java#L309-L329
|
||||
|
||||
@@ -34,6 +34,31 @@ adb shell settings put global stay_on_while_plugged_in 0
|
||||
```
|
||||
|
||||
|
||||
## Screen off timeout
|
||||
|
||||
The Android screen automatically turns off after some delay.
|
||||
|
||||
To change this delay while scrcpy is running:
|
||||
|
||||
```bash
|
||||
scrcpy --screen-off-timeout=300 # 300 seconds (5 minutes)
|
||||
```
|
||||
|
||||
The initial value is restored on exit.
|
||||
|
||||
It is possible to change this setting manually:
|
||||
|
||||
```bash
|
||||
# get the current screen_off_timeout value
|
||||
adb shell settings get system screen_off_timeout
|
||||
# set a new value (in milliseconds)
|
||||
adb shell settings put system screen_off_timeout 30000
|
||||
```
|
||||
|
||||
Note that the Android value is in milliseconds, but the scrcpy command line
|
||||
argument is in seconds.
|
||||
|
||||
|
||||
## Turn screen off
|
||||
|
||||
It is possible to turn the device screen off while mirroring on start with a
|
||||
@@ -71,31 +96,6 @@ adb shell cmd display power-on 0
|
||||
```
|
||||
|
||||
|
||||
## Screen off timeout
|
||||
|
||||
The Android screen automatically turns off after some delay.
|
||||
|
||||
To change this delay while scrcpy is running:
|
||||
|
||||
```bash
|
||||
scrcpy --screen-off-timeout=300 # 300 seconds (5 minutes)
|
||||
```
|
||||
|
||||
The initial value is restored on exit.
|
||||
|
||||
It is possible to change this setting manually:
|
||||
|
||||
```bash
|
||||
# get the current screen_off_timeout value
|
||||
adb shell settings get system screen_off_timeout
|
||||
# set a new value (in milliseconds)
|
||||
adb shell settings put system screen_off_timeout 30000
|
||||
```
|
||||
|
||||
Note that the Android value is in milliseconds, but the scrcpy command line
|
||||
argument is in seconds.
|
||||
|
||||
|
||||
## Show touches
|
||||
|
||||
For presentations, it may be useful to show physical touches (on the physical
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
|
||||
Download a static build of the [latest release]:
|
||||
|
||||
- [`scrcpy-linux-x86_64-v3.1.tar.gz`][direct-linux-x86_64] (x86_64)
|
||||
<sub>SHA-256: `37dba54092ed9ec6b2f8f95432f61b8ea124aec9f1e9f2b3d22d4b10bb04c59a`</sub>
|
||||
- [`scrcpy-linux-x86_64-v3.3.3.tar.gz`][direct-linux-x86_64] (x86_64)
|
||||
<sub>SHA-256: `9b30e813e8191329ba8025dc80cb0f198fb0a318960a3b5c15395cf675c9c638`</sub>
|
||||
|
||||
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
|
||||
[direct-linux-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-linux-x86_64-v3.1.tar.gz
|
||||
[direct-linux-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-linux-x86_64-v3.3.3.tar.gz
|
||||
|
||||
and extract it.
|
||||
|
||||
@@ -27,7 +27,7 @@ Scrcpy is packaged in several distributions and package managers:
|
||||
- Arch Linux: `pacman -S scrcpy`
|
||||
- Fedora: `dnf copr enable zeno/scrcpy && dnf install scrcpy`
|
||||
- Gentoo: `emerge scrcpy`
|
||||
- Snap: `snap install scrcpy`
|
||||
- Snap: ~~`snap install scrcpy`~~ _(obsolete version)_
|
||||
- … (see [repology](https://repology.org/project/scrcpy/versions))
|
||||
|
||||
|
||||
|
||||
12
doc/macos.md
12
doc/macos.md
@@ -6,15 +6,15 @@
|
||||
|
||||
Download a static build of the [latest release]:
|
||||
|
||||
- [`scrcpy-macos-aarch64-v3.1.tar.gz`][direct-macos-aarch64] (aarch64)
|
||||
<sub>SHA-256: `478618d940421e5f57942f5479d493ecbb38210682937a200f712aee5f235daf`</sub>
|
||||
- [`scrcpy-macos-aarch64-v3.3.3.tar.gz`][direct-macos-aarch64] (aarch64)
|
||||
<sub>SHA-256: `b93299468f19ae89ac70f7c1453914c41f1f2bcd31f6ab530038da885c19581f`</sub>
|
||||
|
||||
- [`scrcpy-macos-x86_64-v3.1.tar.gz`][direct-macos-x86_64] (x86_64)
|
||||
<sub>SHA-256: `acde98e29c273710ffa469371dbca4a728a44c41c380381f8a54e5b5301b9e87`</sub>
|
||||
- [`scrcpy-macos-x86_64-v3.3.3.tar.gz`][direct-macos-x86_64] (x86_64)
|
||||
<sub>SHA-256: `c767fc1d41e4ae26e40558656570962f474739924fd22ee023d8754889ee4366`</sub>
|
||||
|
||||
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
|
||||
[direct-macos-aarch64]: https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-macos-aarch64-v3.1.tar.gz
|
||||
[direct-macos-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-macos-x86_64-v3.1.tar.gz
|
||||
[direct-macos-aarch64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-macos-aarch64-v3.3.3.tar.gz
|
||||
[direct-macos-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-macos-x86_64-v3.3.3.tar.gz
|
||||
|
||||
and extract it.
|
||||
|
||||
|
||||
12
doc/mouse.md
12
doc/mouse.md
@@ -83,9 +83,9 @@ process like the _adb daemon_).
|
||||
## Mouse bindings
|
||||
|
||||
By default, with SDK mouse:
|
||||
- right-click triggers BACK (or POWER on)
|
||||
- middle-click triggers HOME
|
||||
- the 4th click triggers APP_SWITCH
|
||||
- right-click triggers `BACK` (or `POWER` on)
|
||||
- middle-click triggers `HOME`
|
||||
- the 4th click triggers `APP_SWITCH`
|
||||
- the 5th click expands the notification panel
|
||||
|
||||
The secondary clicks may be forwarded to the device instead by pressing the
|
||||
@@ -121,9 +121,9 @@ Each character must be one of the following:
|
||||
|
||||
- `+`: forward the click to the device
|
||||
- `-`: ignore the click
|
||||
- `b`: trigger shortcut BACK (or turn screen on if off)
|
||||
- `h`: trigger shortcut HOME
|
||||
- `s`: trigger shortcut APP_SWITCH
|
||||
- `b`: trigger shortcut `BACK` (or turn screen on if off)
|
||||
- `h`: trigger shortcut `HOME`
|
||||
- `s`: trigger shortcut `APP_SWITCH`
|
||||
- `n`: trigger shortcut "expand notification panel"
|
||||
|
||||
For example:
|
||||
|
||||
@@ -11,6 +11,8 @@ scrcpy --new-display # use the main display size and density
|
||||
scrcpy --new-display=/240 # use the main display size and 240 dpi
|
||||
```
|
||||
|
||||
The new virtual display is destroyed on exit.
|
||||
|
||||
## Start app
|
||||
|
||||
On some devices, a launcher is available in the virtual display.
|
||||
@@ -61,3 +63,15 @@ To move them to the main display instead, use:
|
||||
```
|
||||
scrcpy --new-display --no-vd-destroy-content
|
||||
```
|
||||
|
||||
|
||||
## Display IME policy
|
||||
|
||||
By default, the virtual display IME appears on the default display.
|
||||
|
||||
To make it appear on the local display, use `--display-ime-policy=local`:
|
||||
|
||||
```bash
|
||||
scrcpy --display-id=1 --display-ime-policy=local
|
||||
scrcpy --new-display --display-ime-policy=local
|
||||
```
|
||||
|
||||
@@ -6,20 +6,26 @@
|
||||
|
||||
Download the [latest release]:
|
||||
|
||||
- [`scrcpy-win64-v3.1.zip`][direct-win64] (64-bit)
|
||||
<sub>SHA-256: `0c05ea395d95cfe36bee974eeb435a3db87ea5594ff738370d5dc3068a9538ca`</sub>
|
||||
- [`scrcpy-win32-v3.1.zip`][direct-win32] (32-bit)
|
||||
<sub>SHA-256: `2b4674ef76719680ac5a9b482d1943bdde3fa25821ad2e98f3c40c347d00d560`</sub>
|
||||
- [`scrcpy-win64-v3.3.3.zip`][direct-win64] (64-bit)
|
||||
<sub>SHA-256: `4b458d33d0436688c69875cd267cae6fa8be08aa3c17772edf3a940a3dc4b17e`</sub>
|
||||
- [`scrcpy-win32-v3.3.3.zip`][direct-win32] (32-bit)
|
||||
<sub>SHA-256: `e3d43e21c0bd6e070381c390c1e4cccd48a1e71ae73a8c217e6e6b8506598c79`</sub>
|
||||
|
||||
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
|
||||
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-win64-v3.1.zip
|
||||
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-win32-v3.1.zip
|
||||
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-win64-v3.3.3.zip
|
||||
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-win32-v3.3.3.zip
|
||||
|
||||
and extract it.
|
||||
|
||||
|
||||
### From a package manager
|
||||
|
||||
From [WinGet] (ADB and other dependencies will be installed alongside scrcpy):
|
||||
|
||||
```bash
|
||||
winget install --exact Genymobile.scrcpy
|
||||
```
|
||||
|
||||
From [Chocolatey]:
|
||||
|
||||
```bash
|
||||
@@ -29,12 +35,12 @@ choco install adb # if you don't have it yet
|
||||
|
||||
From [Scoop]:
|
||||
|
||||
|
||||
```bash
|
||||
scoop install scrcpy
|
||||
scoop install adb # if you don't have it yet
|
||||
```
|
||||
|
||||
[WinGet]: https://github.com/microsoft/winget-cli
|
||||
[Chocolatey]: https://chocolatey.org/
|
||||
[Scoop]: https://scoop.sh
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
set -e
|
||||
|
||||
BUILDDIR=build-auto
|
||||
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-server-v3.1
|
||||
PREBUILT_SERVER_SHA256=958f0944a62f23b1f33a16e9eb14844c1a04b882ca175a738c16d23cb22b86c0
|
||||
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v3.3.3/scrcpy-server-v3.3.3
|
||||
PREBUILT_SERVER_SHA256=7e70323ba7f259649dd4acce97ac4fefbae8102b2c6d91e2e7be613fd5354be0
|
||||
|
||||
echo "[scrcpy] Downloading prebuilt server..."
|
||||
wget "$PREBUILT_SERVER_URL" -O scrcpy-server
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
project('scrcpy', 'c',
|
||||
version: '3.1',
|
||||
version: '3.3.3',
|
||||
meson_version: '>= 0.49',
|
||||
default_options: [
|
||||
'c_std=c11',
|
||||
|
||||
@@ -7,8 +7,8 @@ android {
|
||||
applicationId "com.genymobile.scrcpy"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 35
|
||||
versionCode 30100
|
||||
versionName "3.1"
|
||||
versionCode 30303
|
||||
versionName "3.3.3"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
set -e
|
||||
|
||||
SCRCPY_DEBUG=false
|
||||
SCRCPY_VERSION_NAME=3.1
|
||||
SCRCPY_VERSION_NAME=3.3.3
|
||||
|
||||
PLATFORM=${ANDROID_PLATFORM:-35}
|
||||
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-35.0.0}
|
||||
@@ -47,10 +47,8 @@ EOF
|
||||
|
||||
echo "Generating java from aidl..."
|
||||
cd "$SERVER_DIR/src/main/aidl"
|
||||
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" -I. android/view/IRotationWatcher.aidl
|
||||
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" -I. \
|
||||
android/content/IOnPrimaryClipChangedListener.aidl
|
||||
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" -I. android/view/IDisplayFoldListener.aidl
|
||||
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" -I. -p "$ANDROID_AIDL" \
|
||||
android/view/IDisplayWindowListener.aidl
|
||||
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.view;
|
||||
|
||||
/**
|
||||
* {@hide}
|
||||
*/
|
||||
oneway interface IDisplayFoldListener
|
||||
{
|
||||
/** Called when the foldedness of a display changes */
|
||||
void onDisplayFoldChanged(int displayId, boolean folded);
|
||||
}
|
||||
@@ -48,19 +48,4 @@ oneway interface IDisplayWindowListener {
|
||||
* Called when a display is removed from the hierarchy.
|
||||
*/
|
||||
void onDisplayRemoved(int displayId);
|
||||
|
||||
/**
|
||||
* Called when fixed rotation is started on a display.
|
||||
*/
|
||||
void onFixedRotationStarted(int displayId, int newRotation);
|
||||
|
||||
/**
|
||||
* Called when the previous fixed rotation on a display is finished.
|
||||
*/
|
||||
void onFixedRotationFinished(int displayId);
|
||||
|
||||
/**
|
||||
* Called when the keep clear ares on a display have changed.
|
||||
*/
|
||||
void onKeepClearAreasChanged(int displayId, in List<Rect> restricted, in List<Rect> unrestricted);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user