mirror of
https://github.com/Genymobile/scrcpy.git
synced 2026-02-24 15:24:28 +01:00
Compare commits
170 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
323549e82f | ||
|
|
90d11810e3 | ||
|
|
1f19ec4aec | ||
|
|
8b0206f7be | ||
|
|
f91fa56593 | ||
|
|
23710e04c1 | ||
|
|
51cbd9a5bc | ||
|
|
82e102e036 | ||
|
|
e39d7a06e4 | ||
|
|
be34f37a57 | ||
|
|
f7e74dd04d | ||
|
|
bb6d1c6348 | ||
|
|
2301f64158 | ||
|
|
d7f0e22067 | ||
|
|
77998f543f | ||
|
|
c43cc2ddc3 | ||
|
|
1617979549 | ||
|
|
58818dc860 | ||
|
|
3ef51e691e | ||
|
|
fb77f8556a | ||
|
|
e3f0c21f2a | ||
|
|
930169af4e | ||
|
|
3ce5feb5cb | ||
|
|
42e2264d67 | ||
|
|
c497718a3e | ||
|
|
07f056353b | ||
|
|
1a99a46b57 | ||
|
|
a9de53f0cb | ||
|
|
c07500bb03 | ||
|
|
373b366fa5 | ||
|
|
5607f12f2a | ||
|
|
8ac04d39f4 | ||
|
|
af355804ef | ||
|
|
1018f3e9be | ||
|
|
48fcfdd104 | ||
|
|
553813e97d | ||
|
|
968f178205 | ||
|
|
1b13d0a22d | ||
|
|
078565d40b | ||
|
|
cc7c07d4a3 | ||
|
|
24b46f11a5 | ||
|
|
f348a1f307 | ||
|
|
f8846aa76d | ||
|
|
1e51d2c83f | ||
|
|
826076f753 | ||
|
|
c36433999c | ||
|
|
78cba1b7c2 | ||
|
|
1015b42e53 | ||
|
|
f4cc07da24 | ||
|
|
fde02a7dfa | ||
|
|
dee1fd46a6 | ||
|
|
a90a039f50 | ||
|
|
ab04e0348d | ||
|
|
99b955f390 | ||
|
|
bbb855b5b0 | ||
|
|
4879950a06 | ||
|
|
170b7a02e7 | ||
|
|
9f1aac41a6 | ||
|
|
02989249f6 | ||
|
|
f8e0b9be4b | ||
|
|
ed62ca124c | ||
|
|
3571fe62ed | ||
|
|
bca98133d1 | ||
|
|
881c71b2e8 | ||
|
|
09eed565fc | ||
|
|
b0da401e6d | ||
|
|
dba2a3778f | ||
|
|
cda4387058 | ||
|
|
42632d3f53 | ||
|
|
db2f72a58a | ||
|
|
fb6381f5b9 | ||
|
|
06fd3b4786 | ||
|
|
1d1a4103cc | ||
|
|
5b51396a8c | ||
|
|
7e66062086 | ||
|
|
6f9eb31d52 | ||
|
|
9cfa5b197a | ||
|
|
b08093d1c0 | ||
|
|
7dd9bcaf60 | ||
|
|
3530851071 | ||
|
|
d0047b2110 | ||
|
|
3281fda6ef | ||
|
|
925949d54a | ||
|
|
f3d4fde15b | ||
|
|
eee3f24739 | ||
|
|
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 | ||
|
|
0ba9d35705 | ||
|
|
cac8e9c821 | ||
|
|
1c7680f689 | ||
|
|
5ae01749bf | ||
|
|
1fd57ede1f | ||
|
|
48fc18e380 | ||
|
|
ea6a94d355 |
55
.github/workflows/release.yml
vendored
55
.github/workflows/release.yml
vendored
@@ -72,19 +72,33 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Same as build-linux-x86_64
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \
|
||||
libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \
|
||||
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \
|
||||
libv4l-dev
|
||||
sudo apt install -y meson ninja-build nasm ffmpeg libavcodec-dev \
|
||||
libavdevice-dev libavformat-dev libavutil-dev libswresample-dev \
|
||||
libusb-1.0-0 libusb-1.0-0-dev libv4l-dev \
|
||||
libasound2-dev libpulse-dev \
|
||||
libaudio-dev libfribidi-dev libjack-dev libsndio-dev libx11-dev libxext-dev \
|
||||
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libxtst-dev \
|
||||
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
|
||||
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev libthai-dev \
|
||||
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-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: |
|
||||
@@ -98,13 +112,19 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# https://wiki.libsdl.org/SDL3/README-linux#build-dependencies
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \
|
||||
libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \
|
||||
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \
|
||||
libv4l-dev
|
||||
sudo apt install -y meson ninja-build nasm ffmpeg libavcodec-dev \
|
||||
libavdevice-dev libavformat-dev libavutil-dev libswresample-dev \
|
||||
libusb-1.0-0 libusb-1.0-0-dev libv4l-dev \
|
||||
libasound2-dev libpulse-dev \
|
||||
libaudio-dev libfribidi-dev libjack-dev libsndio-dev libx11-dev libxext-dev \
|
||||
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libxtst-dev \
|
||||
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
|
||||
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev libthai-dev \
|
||||
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev
|
||||
|
||||
- name: Build
|
||||
run: release/build_linux.sh x86_64
|
||||
@@ -132,9 +152,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \
|
||||
libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \
|
||||
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \
|
||||
sudo apt install -y meson ninja-build nasm \
|
||||
mingw-w64 mingw-w64-tools libz-mingw-w64-dev
|
||||
|
||||
- name: Build
|
||||
@@ -163,9 +181,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \
|
||||
libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \
|
||||
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \
|
||||
sudo apt install -y meson ninja-build nasm \
|
||||
mingw-w64 mingw-w64-tools libz-mingw-w64-dev
|
||||
|
||||
- name: Build
|
||||
@@ -202,8 +218,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:
|
||||
@@ -230,7 +245,7 @@ jobs:
|
||||
path: release/work/build-macos-aarch64/dist-tar/
|
||||
|
||||
build-macos-x86_64:
|
||||
runs-on: macos-13
|
||||
runs-on: macos-15-intel
|
||||
steps:
|
||||
- name: Check architecture
|
||||
run: |
|
||||
@@ -245,7 +260,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.
|
||||
|
||||
28
README.md
28
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.4)
|
||||
|
||||
<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.
|
||||
@@ -119,7 +129,7 @@ Here are just some common examples.
|
||||
scrcpy --otg
|
||||
```
|
||||
|
||||
- Control the device using gamepad controllers plugged into the computer:
|
||||
- Control the device using gamepads plugged into the computer:
|
||||
|
||||
```bash
|
||||
scrcpy --gamepad=uhid
|
||||
@@ -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.
|
||||
|
||||
@@ -18,11 +18,14 @@ _scrcpy() {
|
||||
--camera-fps=
|
||||
--camera-high-speed
|
||||
--camera-size=
|
||||
--camera-torch
|
||||
--camera-zoom=
|
||||
--capture-orientation=
|
||||
--crop=
|
||||
-d --select-usb
|
||||
--disable-screensaver
|
||||
--display-id=
|
||||
--display-ime-policy=
|
||||
--display-orientation=
|
||||
-e --select-tcpip
|
||||
-f --fullscreen
|
||||
@@ -121,7 +124,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 +151,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
|
||||
@@ -192,6 +199,8 @@ _scrcpy() {
|
||||
|--camera-id \
|
||||
|--camera-fps \
|
||||
|--camera-size \
|
||||
|--camera-torch \
|
||||
|--camera-zoom \
|
||||
|--crop \
|
||||
|--display-id \
|
||||
|--max-fps \
|
||||
@@ -200,6 +209,7 @@ _scrcpy() {
|
||||
|-p|--port \
|
||||
|--push-target \
|
||||
|--rotation \
|
||||
|--screen-off-timeout \
|
||||
|--tunnel-host \
|
||||
|--tunnel-port \
|
||||
|--v4l2-buffer \
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
@echo off
|
||||
scrcpy.exe --pause-on-exit=if-error %*
|
||||
@@ -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]'
|
||||
@@ -25,19 +25,22 @@ arguments=(
|
||||
'--camera-facing=[Select the device camera by its facing direction]:facing:(front back external)'
|
||||
'--camera-fps=[Specify the camera capture frame rate]'
|
||||
'--camera-size=[Specify an explicit camera capture size]'
|
||||
'--camera-torch[Turn on the camera torch when the camera starts]'
|
||||
'--camera-zoom[Specify the camera zoom initial value]'
|
||||
'--capture-orientation=[Set the capture video orientation]:orientation:(0 90 180 270 flip0 flip90 flip180 flip270 @0 @90 @180 @270 @flip0 @flip90 @flip180 @flip270)'
|
||||
'--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]'
|
||||
{-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 +50,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]'
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
# This file is intended to be sourced by other scripts, not executed
|
||||
|
||||
process_args() {
|
||||
if [[ $# != 3 ]]
|
||||
then
|
||||
# <host>: win32 or win64
|
||||
# <host>: linux, macos, win32 or win64
|
||||
# <build_type>: native or cross
|
||||
# <link_type>: static or shared
|
||||
echo "Syntax: $0 <host> <build_type> <link_type>" >&2
|
||||
@@ -12,8 +11,8 @@ process_args() {
|
||||
fi
|
||||
|
||||
HOST="$1"
|
||||
BUILD_TYPE="$2" # native or cross
|
||||
LINK_TYPE="$3" # static or shared
|
||||
BUILD_TYPE="$2"
|
||||
LINK_TYPE="$3"
|
||||
DIRNAME="$HOST-$BUILD_TYPE-$LINK_TYPE"
|
||||
|
||||
if [[ "$BUILD_TYPE" != native && "$BUILD_TYPE" != cross ]]
|
||||
@@ -1,13 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
|
||||
cd "$DEPS_DIR"
|
||||
. common
|
||||
. $(dirname ${BASH_SOURCE[0]})/_init "$@"
|
||||
|
||||
VERSION=35.0.2
|
||||
FILENAME=platform-tools_r$VERSION-linux.zip
|
||||
PROJECT_DIR=platform-tools-$VERSION-linux
|
||||
SHA256SUM=acfdcccb123a8718c46c46c059b2f621140194e5ec1ac9d81715be3d6ab6cd0a
|
||||
VERSION=36.0.0
|
||||
URL="https://dl.google.com/android/repository/platform-tools_r$VERSION-linux.zip"
|
||||
SHA256SUM=0ead642c943ffe79701fccca8f5f1c69c4ce4f43df2eefee553f6ccb27cbfbe8
|
||||
|
||||
PROJECT_DIR="platform-tools-$VERSION-linux"
|
||||
FILENAME="$PROJECT_DIR.zip"
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
@@ -15,7 +15,7 @@ if [[ -d "$PROJECT_DIR" ]]
|
||||
then
|
||||
echo "$PWD/$PROJECT_DIR" found
|
||||
else
|
||||
get_file "https://dl.google.com/android/repository/$FILENAME" "$FILENAME" "$SHA256SUM"
|
||||
get_file "$URL" "$FILENAME" "$SHA256SUM"
|
||||
mkdir -p "$PROJECT_DIR"
|
||||
cd "$PROJECT_DIR"
|
||||
ZIP_PREFIX=platform-tools
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
|
||||
cd "$DEPS_DIR"
|
||||
. common
|
||||
. $(dirname ${BASH_SOURCE[0]})/_init "$@"
|
||||
|
||||
VERSION=35.0.2
|
||||
FILENAME=platform-tools_r$VERSION-darwin.zip
|
||||
PROJECT_DIR=platform-tools-$VERSION-darwin
|
||||
SHA256SUM=1820078db90bf21628d257ff052528af1c61bb48f754b3555648f5652fa35d78
|
||||
VERSION=36.0.0
|
||||
URL="https://dl.google.com/android/repository/platform-tools_r$VERSION-darwin.zip"
|
||||
SHA256SUM=d3e9fa1df3345cf728586908426615a60863d2632f73f1ce14f0f1349ef000fd
|
||||
|
||||
PROJECT_DIR="platform-tools-$VERSION-darwin"
|
||||
FILENAME="$PROJECT_DIR.zip"
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
@@ -15,7 +15,7 @@ if [[ -d "$PROJECT_DIR" ]]
|
||||
then
|
||||
echo "$PWD/$PROJECT_DIR" found
|
||||
else
|
||||
get_file "https://dl.google.com/android/repository/$FILENAME" "$FILENAME" "$SHA256SUM"
|
||||
get_file "$URL" "$FILENAME" "$SHA256SUM"
|
||||
mkdir -p "$PROJECT_DIR"
|
||||
cd "$PROJECT_DIR"
|
||||
ZIP_PREFIX=platform-tools
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
|
||||
cd "$DEPS_DIR"
|
||||
. common
|
||||
. $(dirname ${BASH_SOURCE[0]})/_init "$@"
|
||||
|
||||
VERSION=35.0.2
|
||||
FILENAME=platform-tools_r$VERSION-win.zip
|
||||
PROJECT_DIR=platform-tools-$VERSION-windows
|
||||
SHA256SUM=2975a3eac0b19182748d64195375ad056986561d994fffbdc64332a516300bb9
|
||||
VERSION=36.0.0
|
||||
URL="https://dl.google.com/android/repository/platform-tools_r$VERSION-win.zip"
|
||||
SHA256SUM=12c2841f354e92a0eb2fd7bf6f0f9bf8538abce7bd6b060ac8349d6f6a61107c
|
||||
|
||||
PROJECT_DIR="platform-tools-$VERSION-windows"
|
||||
FILENAME="$PROJECT_DIR.zip"
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
@@ -15,7 +15,7 @@ if [[ -d "$PROJECT_DIR" ]]
|
||||
then
|
||||
echo "$PWD/$PROJECT_DIR" found
|
||||
else
|
||||
get_file "https://dl.google.com/android/repository/$FILENAME" "$FILENAME" "$SHA256SUM"
|
||||
get_file "$URL" "$FILENAME" "$SHA256SUM"
|
||||
mkdir -p "$PROJECT_DIR"
|
||||
cd "$PROJECT_DIR"
|
||||
ZIP_PREFIX=platform-tools
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
|
||||
cd "$DEPS_DIR"
|
||||
. common
|
||||
. $(dirname ${BASH_SOURCE[0]})/_init
|
||||
process_args "$@"
|
||||
|
||||
VERSION=1.5.0
|
||||
FILENAME=dav1d-$VERSION.tar.gz
|
||||
PROJECT_DIR=dav1d-$VERSION
|
||||
URL="https://code.videolan.org/videolan/dav1d/-/archive/$VERSION/dav1d-$VERSION.tar.gz"
|
||||
SHA256SUM=78b15d9954b513ea92d27f39362535ded2243e1b0924fde39f37a31ebed5f76b
|
||||
|
||||
PROJECT_DIR="dav1d-$VERSION"
|
||||
FILENAME="$PROJECT_DIR.tar.gz"
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
if [[ -d "$PROJECT_DIR" ]]
|
||||
then
|
||||
echo "$PWD/$PROJECT_DIR" found
|
||||
else
|
||||
get_file "https://code.videolan.org/videolan/dav1d/-/archive/$VERSION/$FILENAME" "$FILENAME" "$SHA256SUM"
|
||||
get_file "$URL" "$FILENAME" "$SHA256SUM"
|
||||
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
|
||||
fi
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
|
||||
cd "$DEPS_DIR"
|
||||
. common
|
||||
. $(dirname ${BASH_SOURCE[0]})/_init
|
||||
process_args "$@"
|
||||
|
||||
VERSION=7.1
|
||||
FILENAME=ffmpeg-$VERSION.tar.xz
|
||||
PROJECT_DIR=ffmpeg-$VERSION
|
||||
SHA256SUM=40973D44970DBC83EF302B0609F2E74982BE2D85916DD2EE7472D30678A7ABE6
|
||||
VERSION=7.1.1
|
||||
URL="https://ffmpeg.org/releases/ffmpeg-$VERSION.tar.xz"
|
||||
SHA256SUM=733984395e0dbbe5c046abda2dc49a5544e7e0e1e2366bba849222ae9e3a03b1
|
||||
|
||||
PROJECT_DIR="ffmpeg-$VERSION"
|
||||
FILENAME="$PROJECT_DIR.tar.xz"
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
@@ -16,7 +16,7 @@ if [[ -d "$PROJECT_DIR" ]]
|
||||
then
|
||||
echo "$PWD/$PROJECT_DIR" found
|
||||
else
|
||||
get_file "https://ffmpeg.org/releases/$FILENAME" "$FILENAME" "$SHA256SUM"
|
||||
get_file "$URL" "$FILENAME" "$SHA256SUM"
|
||||
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
|
||||
fi
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
|
||||
cd "$DEPS_DIR"
|
||||
. common
|
||||
. $(dirname ${BASH_SOURCE[0]})/_init
|
||||
process_args "$@"
|
||||
|
||||
VERSION=1.0.27
|
||||
FILENAME=libusb-$VERSION.tar.gz
|
||||
PROJECT_DIR=libusb-$VERSION
|
||||
SHA256SUM=e8f18a7a36ecbb11fb820bd71540350d8f61bcd9db0d2e8c18a6fb80b214a3de
|
||||
VERSION=1.0.29
|
||||
URL="https://github.com/libusb/libusb/archive/refs/tags/v$VERSION.tar.gz"
|
||||
SHA256SUM=7c2dd39c0b2589236e48c93247c986ae272e27570942b4163cb00a060fcf1b74
|
||||
|
||||
PROJECT_DIR="libusb-$VERSION"
|
||||
FILENAME="$PROJECT_DIR.tar.gz"
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
@@ -16,7 +16,7 @@ if [[ -d "$PROJECT_DIR" ]]
|
||||
then
|
||||
echo "$PWD/$PROJECT_DIR" found
|
||||
else
|
||||
get_file "https://github.com/libusb/libusb/archive/refs/tags/v$VERSION.tar.gz" "$FILENAME" "$SHA256SUM"
|
||||
get_file "$URL" "$FILENAME" "$SHA256SUM"
|
||||
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
|
||||
fi
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
|
||||
cd "$DEPS_DIR"
|
||||
. common
|
||||
. $(dirname ${BASH_SOURCE[0]})/_init
|
||||
process_args "$@"
|
||||
|
||||
VERSION=2.30.10
|
||||
FILENAME=SDL-$VERSION.tar.gz
|
||||
PROJECT_DIR=SDL-release-$VERSION
|
||||
SHA256SUM=35a8b9c4f3635d85762b904ac60ca4e0806bff89faeb269caafbe80860d67168
|
||||
VERSION=3.4.0
|
||||
URL="https://github.com/libsdl-org/SDL/archive/refs/tags/release-$VERSION.tar.gz"
|
||||
SHA256SUM=9614b9696abc4597ffce6b888829dc6537ae500423474c342ac4a67222c5654c
|
||||
|
||||
PROJECT_DIR="sdl-$VERSION"
|
||||
FILENAME="$PROJECT_DIR.tar.gz"
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
@@ -16,8 +16,9 @@ if [[ -d "$PROJECT_DIR" ]]
|
||||
then
|
||||
echo "$PWD/$PROJECT_DIR" found
|
||||
else
|
||||
get_file "https://github.com/libsdl-org/SDL/archive/refs/tags/release-$VERSION.tar.gz" "$FILENAME" "$SHA256SUM"
|
||||
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
|
||||
get_file "$URL" "$FILENAME" "$SHA256SUM"
|
||||
tar xf "$FILENAME" # First level directory is "SDL-release-$VERSION"
|
||||
mv "SDL-release-$VERSION" "$PROJECT_DIR"
|
||||
fi
|
||||
|
||||
mkdir -p "$BUILD_DIR/$PROJECT_DIR"
|
||||
@@ -28,52 +29,56 @@ 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"
|
||||
-DSDL_TESTS=OFF
|
||||
)
|
||||
|
||||
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 .
|
||||
|
||||
@@ -15,7 +15,6 @@ src = [
|
||||
'src/delay_buffer.c',
|
||||
'src/demuxer.c',
|
||||
'src/device_msg.c',
|
||||
'src/display.c',
|
||||
'src/events.c',
|
||||
'src/icon.c',
|
||||
'src/file_pusher.c',
|
||||
@@ -33,6 +32,7 @@ src = [
|
||||
'src/scrcpy.c',
|
||||
'src/screen.c',
|
||||
'src/server.c',
|
||||
'src/texture.c',
|
||||
'src/version.c',
|
||||
'src/hid/hid_gamepad.c',
|
||||
'src/hid/hid_keyboard.c',
|
||||
@@ -57,6 +57,7 @@ src = [
|
||||
'src/util/process.c',
|
||||
'src/util/process_intr.c',
|
||||
'src/util/rand.c',
|
||||
'src/util/sdl.c',
|
||||
'src/util/strbuf.c',
|
||||
'src/util/str.c',
|
||||
'src/util/term.c',
|
||||
@@ -74,6 +75,7 @@ conf.set('_GNU_SOURCE', true)
|
||||
if host_machine.system() == 'windows'
|
||||
windows = import('windows')
|
||||
src += [
|
||||
'src/util/command.c',
|
||||
'src/sys/win/file.c',
|
||||
'src/sys/win/process.c',
|
||||
windows.compile_resources('scrcpy-windows.rc'),
|
||||
@@ -103,7 +105,6 @@ if usb_support
|
||||
'src/usb/keyboard_aoa.c',
|
||||
'src/usb/mouse_aoa.c',
|
||||
'src/usb/scrcpy_otg.c',
|
||||
'src/usb/screen_otg.c',
|
||||
'src/usb/usb.c',
|
||||
]
|
||||
endif
|
||||
@@ -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
|
||||
@@ -238,6 +239,12 @@ if get_option('buildtype') == 'debug'
|
||||
'src/util/strbuf.c',
|
||||
'src/util/term.c',
|
||||
]],
|
||||
['test_command_windows', [
|
||||
'tests/test_command_windows.c',
|
||||
'src/util/command.c',
|
||||
'src/util/str.c',
|
||||
'src/util/strbuf.c',
|
||||
]],
|
||||
['test_control_msg_serialize', [
|
||||
'tests/test_control_msg_serialize.c',
|
||||
'src/control_msg.c',
|
||||
@@ -275,7 +282,7 @@ if get_option('buildtype') == 'debug'
|
||||
exe = executable(t[0], sources,
|
||||
include_directories: src_dir,
|
||||
dependencies: dependencies,
|
||||
c_args: ['-DSDL_MAIN_HANDLED', '-DSC_TEST'])
|
||||
c_args: ['-DSC_TEST'])
|
||||
test(t[0], exe)
|
||||
endforeach
|
||||
endif
|
||||
|
||||
@@ -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.4"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
61
app/scrcpy.1
61
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.
|
||||
|
||||
@@ -125,6 +131,14 @@ The available camera ids can be listed by \fB\-\-list\-cameras\fR.
|
||||
.BI "\-\-camera\-size " width\fRx\fIheight
|
||||
Specify an explicit camera capture size.
|
||||
|
||||
.TP
|
||||
.BI \-\-camera\-torch
|
||||
Turn on the camera torch when the camera starts.
|
||||
|
||||
.TP
|
||||
.BI "\-\-camera-zoom " zoom
|
||||
Specify the camera zoom initial value.
|
||||
|
||||
.TP
|
||||
.BI "\-\-capture\-orientation " value
|
||||
Possible values are 0, 90, 180, 270, flip0, flip90, flip180 and flip270, possibly prefixed by '@'.
|
||||
@@ -161,6 +175,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.
|
||||
@@ -491,6 +518,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".
|
||||
@@ -792,6 +823,22 @@ Install APK from computer
|
||||
.B Drag & drop non-APK file
|
||||
Push file to device (see \fB\-\-push\-target\fR)
|
||||
|
||||
.TP
|
||||
.B MOD+t
|
||||
Turn on the camera torch (camera mode only)
|
||||
|
||||
.TP
|
||||
.B MOD+Shift+t
|
||||
Turn off the camera torch (camera mode only)
|
||||
|
||||
.TP
|
||||
.B MOD+Up
|
||||
Zoom camera in (camera mode only)
|
||||
|
||||
.TP
|
||||
.B MOD+Down
|
||||
Zoom camera out (camera mode only)
|
||||
|
||||
|
||||
.SH Environment variables
|
||||
|
||||
@@ -829,7 +876,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,56 +331,24 @@ 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__
|
||||
// Windows will parse the string, so the paths must be quoted
|
||||
// (see sys/win/command.c)
|
||||
local = sc_str_quote(local);
|
||||
if (!local) {
|
||||
return SC_PROCESS_NONE;
|
||||
}
|
||||
remote = sc_str_quote(remote);
|
||||
if (!remote) {
|
||||
free((void *) local);
|
||||
return SC_PROCESS_NONE;
|
||||
}
|
||||
#endif
|
||||
|
||||
assert(serial);
|
||||
const char *const argv[] =
|
||||
SC_ADB_COMMAND("-s", serial, "push", local, remote);
|
||||
|
||||
sc_pid pid = sc_adb_execute(argv, flags);
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
free((void *) remote);
|
||||
free((void *) local);
|
||||
#endif
|
||||
|
||||
return process_check_success_intr(intr, pid, "adb push", flags);
|
||||
}
|
||||
|
||||
bool
|
||||
sc_adb_install(struct sc_intr *intr, const char *serial, const char *local,
|
||||
unsigned flags) {
|
||||
#ifdef __WINDOWS__
|
||||
// Windows will parse the string, so the local name must be quoted
|
||||
// (see sys/win/command.c)
|
||||
local = sc_str_quote(local);
|
||||
if (!local) {
|
||||
return SC_PROCESS_NONE;
|
||||
}
|
||||
#endif
|
||||
|
||||
assert(serial);
|
||||
const char *const argv[] =
|
||||
SC_ADB_COMMAND("-s", serial, "install", "-r", local);
|
||||
|
||||
sc_pid pid = sc_adb_execute(argv, flags);
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
free((void *) local);
|
||||
#endif
|
||||
|
||||
return process_check_success_intr(intr, pid, "adb install", flags);
|
||||
}
|
||||
|
||||
|
||||
@@ -39,5 +39,11 @@ sc_adb_device_get_type(const char *serial) {
|
||||
return SC_ADB_DEVICE_TYPE_TCPIP;
|
||||
}
|
||||
|
||||
// TCP/IP devices provided by mDNS contain "adb-tls-connect"
|
||||
// <https://github.com/Genymobile/scrcpy/issues/6248>
|
||||
if (strstr(serial, "adb-tls-connect")) {
|
||||
return SC_ADB_DEVICE_TYPE_TCPIP;
|
||||
}
|
||||
|
||||
return SC_ADB_DEVICE_TYPE_USB;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,49 @@
|
||||
#include "util/log.h"
|
||||
#include "util/str.h"
|
||||
|
||||
static size_t
|
||||
rstrip_len(const char *s, size_t len) {
|
||||
size_t i = len;
|
||||
|
||||
// Ignore trailing whitespaces
|
||||
while (i > 0 && (s[i-1] == ' ' || s[i-1] == '\t')) {
|
||||
--i;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
static void
|
||||
locate_last_token(const char *s, size_t len, size_t *start, size_t *end) {
|
||||
size_t i = rstrip_len(s, len);
|
||||
*end = i; // excluded
|
||||
|
||||
// The token contains non-whitespace chars
|
||||
while (i > 0 && (s[i-1] != ' ' && s[i-1] != '\t')) {
|
||||
--i;
|
||||
}
|
||||
|
||||
*start = i; // included
|
||||
}
|
||||
|
||||
static bool
|
||||
is_device_state(const char *s) {
|
||||
// <https://android.googlesource.com/platform/packages/modules/adb/+/1cf2f017d312f73b3dc53bda85ef2610e35a80e9/adb.cpp#144>
|
||||
// "device", "unauthorized" and "offline" are the most common states, so
|
||||
// check them first.
|
||||
return !strcmp(s, "device")
|
||||
|| !strcmp(s, "unauthorized")
|
||||
|| !strcmp(s, "offline")
|
||||
|| !strcmp(s, "bootloader")
|
||||
|| !strcmp(s, "host")
|
||||
|| !strcmp(s, "recovery")
|
||||
|| !strcmp(s, "rescue")
|
||||
|| !strcmp(s, "sideload")
|
||||
|| !strcmp(s, "authorizing")
|
||||
|| !strcmp(s, "connecting")
|
||||
|| !strcmp(s, "detached");
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_adb_parse_device(char *line, struct sc_adb_device *device) {
|
||||
// One device line looks like:
|
||||
@@ -25,64 +68,54 @@ sc_adb_parse_device(char *line, struct sc_adb_device *device) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char *s = line; // cursor in the line
|
||||
size_t len = strlen(line);
|
||||
|
||||
// After the serial:
|
||||
// - "adb devices" writes a single '\t'
|
||||
// - "adb devices -l" writes multiple spaces
|
||||
// For flexibility, accept both.
|
||||
size_t serial_len = strcspn(s, " \t");
|
||||
size_t start;
|
||||
size_t end;
|
||||
|
||||
// The serial (the first token) may contain spaces, which are also token
|
||||
// separators. To avoid ambiguity, parse the string backwards:
|
||||
// - first, parse all the trailing values until the device state,
|
||||
// identified using a list of well-known values;
|
||||
// - finally, treat the remaining leading token as the device serial.
|
||||
//
|
||||
// Refs:
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/6248>
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/3537>
|
||||
const char *state;
|
||||
const char *model = NULL;
|
||||
for (;;) {
|
||||
locate_last_token(line, len, &start, &end);
|
||||
if (start == end) {
|
||||
// No more tokens, unexpected
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *token = &line[start];
|
||||
line[end] = '\0';
|
||||
|
||||
if (!strncmp("model:", token, sizeof("model:") - 1)) {
|
||||
model = &token[sizeof("model:") - 1];
|
||||
// We only need the model
|
||||
} else if (is_device_state(token)) {
|
||||
state = token;
|
||||
// The device state is the item immediately after the device serial
|
||||
break;
|
||||
}
|
||||
|
||||
// Remove the trailing parts already handled
|
||||
len = start;
|
||||
}
|
||||
|
||||
assert(state);
|
||||
|
||||
size_t serial_len = rstrip_len(line, start);
|
||||
if (!serial_len) {
|
||||
// empty serial
|
||||
return false;
|
||||
}
|
||||
bool eol = s[serial_len] == '\0';
|
||||
if (eol) {
|
||||
// serial alone is unexpected
|
||||
return false;
|
||||
}
|
||||
s[serial_len] = '\0';
|
||||
char *serial = s;
|
||||
s += serial_len + 1;
|
||||
// After the serial, there might be several spaces
|
||||
s += strspn(s, " \t"); // consume all separators
|
||||
|
||||
size_t state_len = strcspn(s, " ");
|
||||
if (!state_len) {
|
||||
// empty state
|
||||
return false;
|
||||
}
|
||||
eol = s[state_len] == '\0';
|
||||
s[state_len] = '\0';
|
||||
char *state = s;
|
||||
|
||||
char *model = NULL;
|
||||
if (!eol) {
|
||||
s += state_len + 1;
|
||||
|
||||
// Iterate over all properties "key:value key:value ..."
|
||||
for (;;) {
|
||||
size_t token_len = strcspn(s, " ");
|
||||
if (!token_len) {
|
||||
break;
|
||||
}
|
||||
eol = s[token_len] == '\0';
|
||||
s[token_len] = '\0';
|
||||
char *token = s;
|
||||
|
||||
if (!strncmp("model:", token, sizeof("model:") - 1)) {
|
||||
model = &token[sizeof("model:") - 1];
|
||||
// We only need the model
|
||||
break;
|
||||
}
|
||||
|
||||
if (eol) {
|
||||
break;
|
||||
} else {
|
||||
s+= token_len + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
char *serial = line;
|
||||
line[serial_len] = '\0';
|
||||
|
||||
device->serial = strdup(serial);
|
||||
if (!device->serial) {
|
||||
|
||||
@@ -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,32 +81,53 @@ 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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
if (!ok) {
|
||||
ok = sc_thread_set_priority(SC_THREAD_PRIORITY_HIGH);
|
||||
(void) ok; // We don't care if it worked, at least we tried
|
||||
// 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_PauseAudioDevice(ap->device, 0);
|
||||
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);
|
||||
|
||||
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 +136,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
|
||||
|
||||
221
app/src/cli.c
221
app/src/cli.c
@@ -113,6 +113,9 @@ enum {
|
||||
OPT_ANGLE,
|
||||
OPT_NO_VD_SYSTEM_DECORATIONS,
|
||||
OPT_NO_VD_DESTROY_CONTENT,
|
||||
OPT_DISPLAY_IME_POLICY,
|
||||
OPT_CAMERA_TORCH,
|
||||
OPT_CAMERA_ZOOM,
|
||||
};
|
||||
|
||||
struct sc_option {
|
||||
@@ -216,13 +219,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.",
|
||||
},
|
||||
{
|
||||
@@ -294,6 +315,17 @@ static const struct sc_option options[] = {
|
||||
.argdesc = "<width>x<height>",
|
||||
.text = "Specify an explicit camera capture size.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_CAMERA_TORCH,
|
||||
.longopt = "camera-torch",
|
||||
.text = "Turn on the camera torch when the camera starts.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_CAMERA_ZOOM,
|
||||
.longopt = "camera-zoom",
|
||||
.argdesc = "zoom",
|
||||
.text = "Specify the camera zoom initial value.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_CAPTURE_ORIENTATION,
|
||||
.longopt = "capture-orientation",
|
||||
@@ -366,6 +398,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",
|
||||
@@ -1175,6 +1220,22 @@ static const struct sc_shortcut shortcuts[] = {
|
||||
.shortcuts = { "Drag & drop non-APK file" },
|
||||
.text = "Push file to device (see --push-target)",
|
||||
},
|
||||
{
|
||||
.shortcuts = { "MOD+t" },
|
||||
.text = "Turn on the camera torch (camera mode only)",
|
||||
},
|
||||
{
|
||||
.shortcuts = { "MOD+Shift+t" },
|
||||
.text = "Turn off the camera torch (camera mode only)",
|
||||
},
|
||||
{
|
||||
.shortcuts = { "MOD+Up" },
|
||||
.text = "Zoom camera in (camera mode only)",
|
||||
},
|
||||
{
|
||||
.shortcuts = { "MOD+Down" },
|
||||
.text = "Zoom camera out (camera mode only)",
|
||||
},
|
||||
};
|
||||
|
||||
static const struct sc_envvar envvars[] = {
|
||||
@@ -1614,6 +1675,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")) {
|
||||
@@ -2003,8 +2083,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;
|
||||
}
|
||||
|
||||
@@ -2687,6 +2809,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
case OPT_CAMERA_HIGH_SPEED:
|
||||
opts->camera_high_speed = true;
|
||||
break;
|
||||
case OPT_CAMERA_TORCH:
|
||||
opts->camera_torch = true;
|
||||
break;
|
||||
case OPT_CAMERA_ZOOM:
|
||||
opts->camera_zoom = optarg;
|
||||
break;
|
||||
case OPT_NO_WINDOW:
|
||||
opts->window = false;
|
||||
break;
|
||||
@@ -2722,6 +2850,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;
|
||||
@@ -2829,7 +2963,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
}
|
||||
#endif
|
||||
|
||||
if (opts->control) {
|
||||
if (opts->control && opts->video_source == SC_VIDEO_SOURCE_DISPLAY) {
|
||||
if (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AUTO) {
|
||||
opts->keyboard_input_mode = otg ? SC_KEYBOARD_INPUT_MODE_AOA
|
||||
: SC_KEYBOARD_INPUT_MODE_SDK;
|
||||
@@ -2978,6 +3112,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;
|
||||
@@ -3001,8 +3141,10 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
}
|
||||
|
||||
if (opts->control) {
|
||||
LOGI("Camera video source: control disabled");
|
||||
opts->control = false;
|
||||
// Disable all inputs for camera
|
||||
opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_DISABLED;
|
||||
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_DISABLED;
|
||||
opts->gamepad_input_mode = SC_GAMEPAD_INPUT_MODE_DISABLED;
|
||||
}
|
||||
} else if (opts->camera_id
|
||||
|| opts->camera_ar
|
||||
@@ -3019,6 +3161,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) {
|
||||
@@ -3223,7 +3371,7 @@ sc_get_pause_on_exit(int argc, char *argv[]) {
|
||||
}
|
||||
if (arg[15] != '=') {
|
||||
// Invalid parameter, ignore
|
||||
return SC_PAUSE_ON_EXIT_FALSE;
|
||||
return SC_PAUSE_ON_EXIT_UNDEFINED;
|
||||
}
|
||||
const char *value = &arg[16];
|
||||
if (!strcmp(value, "true")) {
|
||||
@@ -3232,14 +3380,44 @@ sc_get_pause_on_exit(int argc, char *argv[]) {
|
||||
if (!strcmp(value, "if-error")) {
|
||||
return SC_PAUSE_ON_EXIT_IF_ERROR;
|
||||
}
|
||||
// Set to false, including when the value is invalid
|
||||
return SC_PAUSE_ON_EXIT_FALSE;
|
||||
if (!strcmp(value, "false")) {
|
||||
return SC_PAUSE_ON_EXIT_FALSE;
|
||||
}
|
||||
return SC_PAUSE_ON_EXIT_UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
return SC_PAUSE_ON_EXIT_FALSE;
|
||||
return SC_PAUSE_ON_EXIT_UNDEFINED;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
/**
|
||||
* Attempt to detect whether the user launched scrcpy by double-clicking
|
||||
* scrcpy.exe in Windows Explorer.
|
||||
*
|
||||
* If so, the console should remain open on error.
|
||||
*/
|
||||
static bool
|
||||
scrcpy_launched_by_double_click(void) {
|
||||
// No console window
|
||||
if (GetConsoleWindow() == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Must be interactive
|
||||
if (!_isatty(_fileno(stdin)) || !_isatty(_fileno(stdout))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check how many processes share the console
|
||||
DWORD dummy;
|
||||
DWORD count = GetConsoleProcessList(&dummy, 1);
|
||||
|
||||
// Only this process attached, assume it was started by double-clicking
|
||||
return count == 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool
|
||||
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
||||
struct sc_getopt_adapter adapter;
|
||||
@@ -3253,11 +3431,22 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
||||
|
||||
sc_getopt_adapter_destroy(&adapter);
|
||||
|
||||
if (!ret && args->pause_on_exit == SC_PAUSE_ON_EXIT_FALSE) {
|
||||
if (!ret && args->pause_on_exit == SC_PAUSE_ON_EXIT_UNDEFINED) {
|
||||
// Check if "--pause-on-exit" is present in the arguments list, because
|
||||
// it must be taken into account even if command line parsing failed
|
||||
args->pause_on_exit = sc_get_pause_on_exit(argc, argv);
|
||||
}
|
||||
|
||||
if (args->pause_on_exit == SC_PAUSE_ON_EXIT_UNDEFINED) {
|
||||
args->pause_on_exit = SC_PAUSE_ON_EXIT_FALSE;
|
||||
#ifdef _WIN32
|
||||
if (scrcpy_launched_by_double_click()) {
|
||||
args->pause_on_exit = SC_PAUSE_ON_EXIT_IF_ERROR;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
assert(args->pause_on_exit != SC_PAUSE_ON_EXIT_UNDEFINED);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "options.h"
|
||||
|
||||
enum sc_pause_on_exit {
|
||||
SC_PAUSE_ON_EXIT_UNDEFINED,
|
||||
SC_PAUSE_ON_EXIT_TRUE,
|
||||
SC_PAUSE_ON_EXIT_FALSE,
|
||||
SC_PAUSE_ON_EXIT_IF_ERROR,
|
||||
|
||||
@@ -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);
|
||||
@@ -178,12 +182,17 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
|
||||
size_t len = write_string_tiny(&buf[1], msg->start_app.name, 255);
|
||||
return 1 + len;
|
||||
}
|
||||
case SC_CONTROL_MSG_TYPE_CAMERA_SET_TORCH:
|
||||
buf[1] = msg->camera_set_torch.on ? 1 : 0;
|
||||
return 2;
|
||||
case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||
case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
|
||||
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
||||
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
||||
case SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
||||
case SC_CONTROL_MSG_TYPE_RESET_VIDEO:
|
||||
case SC_CONTROL_MSG_TYPE_CAMERA_ZOOM_IN:
|
||||
case SC_CONTROL_MSG_TYPE_CAMERA_ZOOM_OUT:
|
||||
// no additional data
|
||||
return 1;
|
||||
default:
|
||||
@@ -314,6 +323,16 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
|
||||
case SC_CONTROL_MSG_TYPE_RESET_VIDEO:
|
||||
LOG_CMSG("reset video");
|
||||
break;
|
||||
case SC_CONTROL_MSG_TYPE_CAMERA_SET_TORCH:
|
||||
LOG_CMSG("camera set torch %s",
|
||||
msg->camera_set_torch.on ? "on" : "off");
|
||||
break;
|
||||
case SC_CONTROL_MSG_TYPE_CAMERA_ZOOM_IN:
|
||||
LOG_CMSG("camera zoom in");
|
||||
break;
|
||||
case SC_CONTROL_MSG_TYPE_CAMERA_ZOOM_OUT:
|
||||
LOG_CMSG("camera zoom out");
|
||||
break;
|
||||
default:
|
||||
LOG_CMSG("unknown type: %u", (unsigned) msg->type);
|
||||
break;
|
||||
|
||||
@@ -43,6 +43,9 @@ enum sc_control_msg_type {
|
||||
SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS,
|
||||
SC_CONTROL_MSG_TYPE_START_APP,
|
||||
SC_CONTROL_MSG_TYPE_RESET_VIDEO,
|
||||
SC_CONTROL_MSG_TYPE_CAMERA_SET_TORCH,
|
||||
SC_CONTROL_MSG_TYPE_CAMERA_ZOOM_IN,
|
||||
SC_CONTROL_MSG_TYPE_CAMERA_ZOOM_OUT,
|
||||
};
|
||||
|
||||
enum sc_copy_key {
|
||||
@@ -111,6 +114,9 @@ struct sc_control_msg {
|
||||
struct {
|
||||
char *name;
|
||||
} start_app;
|
||||
struct {
|
||||
bool on;
|
||||
} camera_set_torch;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,344 +0,0 @@
|
||||
#include "display.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include <libavutil/pixfmt.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
static bool
|
||||
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)) {
|
||||
LOGW("Could not set renderer logical size: %s", SDL_GetError());
|
||||
// don't fail
|
||||
}
|
||||
|
||||
display->texture = SDL_CreateTextureFromSurface(display->renderer,
|
||||
icon_novideo);
|
||||
if (!display->texture) {
|
||||
LOGE("Could not create texture: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
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;
|
||||
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
|
||||
|
||||
display->mipmaps = false;
|
||||
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
display->gl_context = NULL;
|
||||
#endif
|
||||
|
||||
// starts with "opengl"
|
||||
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
|
||||
if (use_opengl) {
|
||||
|
||||
#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);
|
||||
|
||||
LOGD("Creating OpenGL Core Profile context");
|
||||
display->gl_context = SDL_GL_CreateContext(window);
|
||||
if (!display->gl_context) {
|
||||
LOGE("Could not create OpenGL context: %s", SDL_GetError());
|
||||
SDL_DestroyRenderer(display->renderer);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct sc_opengl *gl = &display->gl;
|
||||
sc_opengl_init(gl);
|
||||
|
||||
LOGI("OpenGL version: %s", gl->version);
|
||||
|
||||
if (mipmaps) {
|
||||
bool supports_mipmaps =
|
||||
sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */
|
||||
2, 0 /* OpenGL ES 2.0+ */);
|
||||
if (supports_mipmaps) {
|
||||
LOGI("Trilinear filtering enabled");
|
||||
display->mipmaps = true;
|
||||
} else {
|
||||
LOGW("Trilinear filtering disabled "
|
||||
"(OpenGL 3.0+ or ES 2.0+ required)");
|
||||
}
|
||||
} else {
|
||||
LOGI("Trilinear filtering disabled");
|
||||
}
|
||||
} else if (mipmaps) {
|
||||
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
|
||||
}
|
||||
|
||||
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);
|
||||
#endif
|
||||
SDL_DestroyRenderer(display->renderer);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
sc_display_destroy(struct sc_display *display) {
|
||||
if (display->pending.frame) {
|
||||
av_frame_free(&display->pending.frame);
|
||||
}
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
SDL_GL_DeleteContext(display->gl_context);
|
||||
#endif
|
||||
if (display->texture) {
|
||||
SDL_DestroyTexture(display->texture);
|
||||
}
|
||||
SDL_DestroyRenderer(display->renderer);
|
||||
}
|
||||
|
||||
static SDL_Texture *
|
||||
sc_display_create_texture(struct sc_display *display,
|
||||
struct sc_size size) {
|
||||
SDL_Renderer *renderer = display->renderer;
|
||||
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
|
||||
SDL_TEXTUREACCESS_STREAMING,
|
||||
size.width, size.height);
|
||||
if (!texture) {
|
||||
LOGD("Could not create texture: %s", SDL_GetError());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (display->mipmaps) {
|
||||
struct sc_opengl *gl = &display->gl;
|
||||
|
||||
SDL_GL_BindTexture(texture, NULL, NULL);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
static inline void
|
||||
sc_display_set_pending_size(struct sc_display *display, struct sc_size size) {
|
||||
assert(!display->texture);
|
||||
display->pending.size = size;
|
||||
display->pending.flags |= SC_DISPLAY_PENDING_FLAG_SIZE;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_display_set_pending_frame(struct sc_display *display, const AVFrame *frame) {
|
||||
if (!display->pending.frame) {
|
||||
display->pending.frame = av_frame_alloc();
|
||||
if (!display->pending.frame) {
|
||||
LOG_OOM();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int r = av_frame_ref(display->pending.frame, frame);
|
||||
if (r) {
|
||||
LOGE("Could not ref frame: %d", r);
|
||||
return false;
|
||||
}
|
||||
|
||||
display->pending.flags |= SC_DISPLAY_PENDING_FLAG_FRAME;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_display_apply_pending(struct sc_display *display) {
|
||||
if (display->pending.flags & SC_DISPLAY_PENDING_FLAG_SIZE) {
|
||||
assert(!display->texture);
|
||||
display->texture =
|
||||
sc_display_create_texture(display, display->pending.size);
|
||||
if (!display->texture) {
|
||||
return false;
|
||||
}
|
||||
|
||||
display->pending.flags &= ~SC_DISPLAY_PENDING_FLAG_SIZE;
|
||||
}
|
||||
|
||||
if (display->pending.flags & SC_DISPLAY_PENDING_FLAG_FRAME) {
|
||||
assert(display->pending.frame);
|
||||
bool ok = sc_display_update_texture(display, display->pending.frame);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
av_frame_unref(display->pending.frame);
|
||||
display->pending.flags &= ~SC_DISPLAY_PENDING_FLAG_FRAME;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_display_set_texture_size_internal(struct sc_display *display,
|
||||
struct sc_size size) {
|
||||
assert(size.width && size.height);
|
||||
|
||||
if (display->texture) {
|
||||
SDL_DestroyTexture(display->texture);
|
||||
}
|
||||
|
||||
display->texture = sc_display_create_texture(display, size);
|
||||
if (!display->texture) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGI("Texture: %" PRIu16 "x%" PRIu16, size.width, size.height);
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
if (!ok) {
|
||||
sc_display_set_pending_size(display, size);
|
||||
return SC_DISPLAY_RESULT_PENDING;
|
||||
|
||||
}
|
||||
|
||||
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,
|
||||
frame->data[0], frame->linesize[0],
|
||||
frame->data[1], frame->linesize[1],
|
||||
frame->data[2], frame->linesize[2]);
|
||||
if (ret) {
|
||||
LOGD("Could not update texture: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (display->mipmaps) {
|
||||
SDL_GL_BindTexture(display->texture, NULL, NULL);
|
||||
display->gl.GenerateMipmap(GL_TEXTURE_2D);
|
||||
SDL_GL_UnbindTexture(display->texture);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
enum sc_display_result
|
||||
sc_display_update_texture(struct sc_display *display, const AVFrame *frame) {
|
||||
bool ok = sc_display_update_texture_internal(display, frame);
|
||||
if (!ok) {
|
||||
ok = sc_display_set_pending_frame(display, frame);
|
||||
if (!ok) {
|
||||
LOGE("Could not set pending frame");
|
||||
return SC_DISPLAY_RESULT_ERROR;
|
||||
}
|
||||
|
||||
return SC_DISPLAY_RESULT_PENDING;
|
||||
}
|
||||
|
||||
return SC_DISPLAY_RESULT_OK;
|
||||
}
|
||||
|
||||
enum sc_display_result
|
||||
sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
|
||||
enum sc_orientation orientation) {
|
||||
SDL_RenderClear(display->renderer);
|
||||
|
||||
if (display->pending.flags) {
|
||||
bool ok = sc_display_apply_pending(display);
|
||||
if (!ok) {
|
||||
return SC_DISPLAY_RESULT_PENDING;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_Renderer *renderer = display->renderer;
|
||||
SDL_Texture *texture = display->texture;
|
||||
|
||||
if (orientation == SC_ORIENTATION_0) {
|
||||
int ret = SDL_RenderCopy(renderer, texture, NULL, geometry);
|
||||
if (ret) {
|
||||
LOGE("Could not render texture: %s", SDL_GetError());
|
||||
return SC_DISPLAY_RESULT_ERROR;
|
||||
}
|
||||
} else {
|
||||
unsigned cw_rotation = sc_orientation_get_rotation(orientation);
|
||||
double angle = 90 * cw_rotation;
|
||||
|
||||
const SDL_Rect *dstrect = NULL;
|
||||
SDL_Rect rect;
|
||||
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 = ▭
|
||||
} else {
|
||||
dstrect = geometry;
|
||||
}
|
||||
|
||||
SDL_RendererFlip flip = sc_orientation_is_mirror(orientation)
|
||||
? SDL_FLIP_HORIZONTAL : 0;
|
||||
|
||||
int ret = SDL_RenderCopyEx(renderer, texture, NULL, dstrect, angle,
|
||||
NULL, flip);
|
||||
if (ret) {
|
||||
LOGE("Could not render texture: %s", SDL_GetError());
|
||||
return SC_DISPLAY_RESULT_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_RenderPresent(display->renderer);
|
||||
return SC_DISPLAY_RESULT_OK;
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
#ifndef SC_DISPLAY_H
|
||||
#define SC_DISPLAY_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <libavutil/frame.h>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include "coords.h"
|
||||
#include "opengl.h"
|
||||
#include "options.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
# define SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
#endif
|
||||
|
||||
struct sc_display {
|
||||
SDL_Renderer *renderer;
|
||||
SDL_Texture *texture;
|
||||
|
||||
struct sc_opengl gl;
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
SDL_GLContext *gl_context;
|
||||
#endif
|
||||
|
||||
bool mipmaps;
|
||||
|
||||
struct {
|
||||
#define SC_DISPLAY_PENDING_FLAG_SIZE 1
|
||||
#define SC_DISPLAY_PENDING_FLAG_FRAME 2
|
||||
int8_t flags;
|
||||
struct sc_size size;
|
||||
AVFrame *frame;
|
||||
} pending;
|
||||
|
||||
bool has_frame;
|
||||
};
|
||||
|
||||
enum sc_display_result {
|
||||
SC_DISPLAY_RESULT_OK,
|
||||
SC_DISPLAY_RESULT_PENDING,
|
||||
SC_DISPLAY_RESULT_ERROR,
|
||||
};
|
||||
|
||||
bool
|
||||
sc_display_init(struct sc_display *display, SDL_Window *window,
|
||||
SDL_Surface *icon_novideo, bool mipmaps);
|
||||
|
||||
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);
|
||||
|
||||
enum sc_display_result
|
||||
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);
|
||||
|
||||
#endif
|
||||
@@ -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,
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "hid/hid_event.h"
|
||||
#include "input_events.h"
|
||||
|
||||
// See "SDL2/SDL_scancode.h".
|
||||
// See "SDL3/SDL_scancode.h".
|
||||
// Maybe SDL_Keycode is used by most people, but SDL_Scancode is taken from USB
|
||||
// HID protocol.
|
||||
// 0x65 is Application, typically AT-101 Keyboard ends here.
|
||||
|
||||
@@ -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 set pointer property: %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"
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
* for simplicity.
|
||||
*
|
||||
* This scrcpy input events API is designed to be consumed by input event
|
||||
* processors (sc_key_processor and sc_mouse_processor, see app/src/trait/).
|
||||
* processors (sc_key_processor, sc_mouse_processor and sc_gamepad_processor,
|
||||
* see app/src/trait/).
|
||||
*
|
||||
* One major semantic difference between SDL input events and scrcpy input
|
||||
* events is their frame of reference (for mouse and touch events): SDL events
|
||||
@@ -43,17 +44,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 +71,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 +108,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,43 +316,40 @@ 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:
|
||||
// <https://wiki.libsdl.org/SDL3/README/migration>
|
||||
|
||||
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),
|
||||
@@ -411,10 +409,9 @@ struct sc_touch_event {
|
||||
float pressure;
|
||||
};
|
||||
|
||||
// As documented in <https://wiki.libsdl.org/SDL2/SDL_JoystickID>:
|
||||
// The ID value starts at 0 and increments from there. The value -1 is an
|
||||
// invalid ID.
|
||||
#define SC_GAMEPAD_ID_INVALID UINT32_C(-1)
|
||||
// As documented in <https://wiki.libsdl.org/SDL3/SDL_JoystickID>:
|
||||
// The value 0 is an invalid ID.
|
||||
#define SC_GAMEPAD_ID_INVALID 0
|
||||
|
||||
struct sc_gamepad_device_event {
|
||||
uint32_t gamepad_id;
|
||||
@@ -449,8 +446,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 +455,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 +464,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 +479,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,9 +495,9 @@ 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_*
|
||||
// SDL_GAMEPAD_AXIS_*
|
||||
return axis;
|
||||
}
|
||||
return SC_GAMEPAD_AXIS_UNKNOWN;
|
||||
@@ -508,18 +505,18 @@ 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_*
|
||||
// SDL_GAMEPAD_BUTTON_*
|
||||
return button;
|
||||
}
|
||||
return SC_GAMEPAD_BUTTON_UNKNOWN;
|
||||
}
|
||||
|
||||
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) {
|
||||
sc_action_from_sdl_gamepad_button_type(uint32_t type) {
|
||||
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"
|
||||
@@ -11,12 +11,11 @@
|
||||
#include "screen.h"
|
||||
#include "shortcut_mod.h"
|
||||
#include "util/log.h"
|
||||
#include "util/sdl.h"
|
||||
|
||||
void
|
||||
sc_input_manager_init(struct sc_input_manager *im,
|
||||
const struct sc_input_manager_params *params) {
|
||||
// A key/mouse processor may not be present if there is no controller
|
||||
assert((!params->kp && !params->mp && !params->gp) || params->controller);
|
||||
// A processor must have ops initialized
|
||||
assert(!params->kp || params->kp->ops);
|
||||
assert(!params->mp || params->mp->ops);
|
||||
@@ -28,6 +27,7 @@ sc_input_manager_init(struct sc_input_manager *im,
|
||||
im->kp = params->kp;
|
||||
im->mp = params->mp;
|
||||
im->gp = params->gp;
|
||||
im->camera = params->camera;
|
||||
|
||||
im->mouse_bindings = params->mouse_bindings;
|
||||
im->legacy_paste = params->legacy_paste;
|
||||
@@ -51,7 +51,7 @@ sc_input_manager_init(struct sc_input_manager *im,
|
||||
static void
|
||||
send_keycode(struct sc_input_manager *im, enum android_keycode keycode,
|
||||
enum sc_action action, const char *name) {
|
||||
assert(im->controller && im->kp);
|
||||
assert(im->controller && im->kp && !im->camera);
|
||||
|
||||
// send DOWN event
|
||||
struct sc_control_msg msg;
|
||||
@@ -108,7 +108,7 @@ action_menu(struct sc_input_manager *im, enum sc_action action) {
|
||||
static void
|
||||
press_back_or_turn_screen_on(struct sc_input_manager *im,
|
||||
enum sc_action action) {
|
||||
assert(im->controller && im->kp);
|
||||
assert(im->controller && im->kp && !im->camera);
|
||||
|
||||
struct sc_control_msg msg;
|
||||
msg.type = SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON;
|
||||
@@ -123,7 +123,7 @@ press_back_or_turn_screen_on(struct sc_input_manager *im,
|
||||
|
||||
static void
|
||||
expand_notification_panel(struct sc_input_manager *im) {
|
||||
assert(im->controller);
|
||||
assert(im->controller && !im->camera);
|
||||
|
||||
struct sc_control_msg msg;
|
||||
msg.type = SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL;
|
||||
@@ -135,7 +135,7 @@ expand_notification_panel(struct sc_input_manager *im) {
|
||||
|
||||
static void
|
||||
expand_settings_panel(struct sc_input_manager *im) {
|
||||
assert(im->controller);
|
||||
assert(im->controller && !im->camera);
|
||||
|
||||
struct sc_control_msg msg;
|
||||
msg.type = SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL;
|
||||
@@ -147,7 +147,7 @@ expand_settings_panel(struct sc_input_manager *im) {
|
||||
|
||||
static void
|
||||
collapse_panels(struct sc_input_manager *im) {
|
||||
assert(im->controller);
|
||||
assert(im->controller && !im->camera);
|
||||
|
||||
struct sc_control_msg msg;
|
||||
msg.type = SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS;
|
||||
@@ -159,7 +159,7 @@ collapse_panels(struct sc_input_manager *im) {
|
||||
|
||||
static bool
|
||||
get_device_clipboard(struct sc_input_manager *im, enum sc_copy_key copy_key) {
|
||||
assert(im->controller && im->kp);
|
||||
assert(im->controller && im->kp && !im->camera);
|
||||
|
||||
struct sc_control_msg msg;
|
||||
msg.type = SC_CONTROL_MSG_TYPE_GET_CLIPBOARD;
|
||||
@@ -176,7 +176,7 @@ get_device_clipboard(struct sc_input_manager *im, enum sc_copy_key copy_key) {
|
||||
static bool
|
||||
set_device_clipboard(struct sc_input_manager *im, bool paste,
|
||||
uint64_t sequence) {
|
||||
assert(im->controller && im->kp);
|
||||
assert(im->controller && im->kp && !im->camera);
|
||||
|
||||
char *text = SDL_GetClipboardText();
|
||||
if (!text) {
|
||||
@@ -208,7 +208,7 @@ set_device_clipboard(struct sc_input_manager *im, bool paste,
|
||||
|
||||
static void
|
||||
set_display_power(struct sc_input_manager *im, bool on) {
|
||||
assert(im->controller);
|
||||
assert(im->controller && !im->camera);
|
||||
|
||||
struct sc_control_msg msg;
|
||||
msg.type = SC_CONTROL_MSG_TYPE_SET_DISPLAY_POWER;
|
||||
@@ -235,7 +235,7 @@ switch_fps_counter_state(struct sc_input_manager *im) {
|
||||
|
||||
static void
|
||||
clipboard_paste(struct sc_input_manager *im) {
|
||||
assert(im->controller && im->kp);
|
||||
assert(im->controller && im->kp && !im->camera);
|
||||
|
||||
char *text = SDL_GetClipboardText();
|
||||
if (!text) {
|
||||
@@ -266,7 +266,7 @@ clipboard_paste(struct sc_input_manager *im) {
|
||||
|
||||
static void
|
||||
rotate_device(struct sc_input_manager *im) {
|
||||
assert(im->controller);
|
||||
assert(im->controller && !im->camera);
|
||||
|
||||
struct sc_control_msg msg;
|
||||
msg.type = SC_CONTROL_MSG_TYPE_ROTATE_DEVICE;
|
||||
@@ -278,7 +278,7 @@ rotate_device(struct sc_input_manager *im) {
|
||||
|
||||
static void
|
||||
open_hard_keyboard_settings(struct sc_input_manager *im) {
|
||||
assert(im->controller);
|
||||
assert(im->controller && !im->camera);
|
||||
|
||||
struct sc_control_msg msg;
|
||||
msg.type = SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS;
|
||||
@@ -300,6 +300,43 @@ reset_video(struct sc_input_manager *im) {
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
camera_set_torch(struct sc_input_manager *im, bool on) {
|
||||
assert(im->controller && im->camera);
|
||||
|
||||
struct sc_control_msg msg;
|
||||
msg.type = SC_CONTROL_MSG_TYPE_CAMERA_SET_TORCH;
|
||||
msg.camera_set_torch.on = on;
|
||||
|
||||
if (!sc_controller_push_msg(im->controller, &msg)) {
|
||||
LOGW("Could not request setting camera torch");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
camera_zoom_in(struct sc_input_manager *im) {
|
||||
assert(im->controller && im->camera);
|
||||
|
||||
struct sc_control_msg msg;
|
||||
msg.type = SC_CONTROL_MSG_TYPE_CAMERA_ZOOM_IN;
|
||||
|
||||
if (!sc_controller_push_msg(im->controller, &msg)) {
|
||||
LOGW("Could not request camera zoom in");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
camera_zoom_out(struct sc_input_manager *im) {
|
||||
assert(im->controller && im->camera);
|
||||
|
||||
struct sc_control_msg msg;
|
||||
msg.type = SC_CONTROL_MSG_TYPE_CAMERA_ZOOM_OUT;
|
||||
|
||||
if (!sc_controller_push_msg(im->controller, &msg)) {
|
||||
LOGW("Could not request camera zoom out");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
apply_orientation_transform(struct sc_input_manager *im,
|
||||
enum sc_orientation transform) {
|
||||
@@ -312,6 +349,10 @@ apply_orientation_transform(struct sc_input_manager *im,
|
||||
static void
|
||||
sc_input_manager_process_text_input(struct sc_input_manager *im,
|
||||
const SDL_TextInputEvent *event) {
|
||||
if (im->camera || !im->kp || im->screen->paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!im->kp->ops->process_text) {
|
||||
// The key processor does not support text input
|
||||
return;
|
||||
@@ -369,16 +410,19 @@ inverse_point(struct sc_point point, struct sc_size size,
|
||||
static void
|
||||
sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
const SDL_KeyboardEvent *event) {
|
||||
// some key events do not interact with the device, so process the event
|
||||
// even if control is disabled
|
||||
|
||||
// controller is NULL if --no-control is requested
|
||||
bool control = im->controller;
|
||||
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,156 +446,196 @@ 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:
|
||||
if (im->kp && !shift && !repeat && !paused) {
|
||||
action_home(im, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_b: // fall-through
|
||||
case SDLK_BACKSPACE:
|
||||
if (im->kp && !shift && !repeat && !paused) {
|
||||
action_back(im, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_s:
|
||||
if (im->kp && !shift && !repeat && !paused) {
|
||||
action_app_switch(im, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_m:
|
||||
if (im->kp && !shift && !repeat && !paused) {
|
||||
action_menu(im, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_p:
|
||||
if (im->kp && !shift && !repeat && !paused) {
|
||||
action_power(im, action);
|
||||
}
|
||||
return;
|
||||
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);
|
||||
}
|
||||
return;
|
||||
case SDLK_DOWN:
|
||||
// Only capture if shift is set
|
||||
if (shift) {
|
||||
if (video && !repeat && down) {
|
||||
apply_orientation_transform(im,
|
||||
SC_ORIENTATION_FLIP_180);
|
||||
}
|
||||
} else if (im->kp && !paused) {
|
||||
// forward repeated events
|
||||
action_volume_down(im, action);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
break;
|
||||
case SDLK_UP:
|
||||
// Only capture if shift is set
|
||||
if (shift) {
|
||||
if (video && !repeat && down) {
|
||||
apply_orientation_transform(im,
|
||||
SC_ORIENTATION_FLIP_180);
|
||||
apply_orientation_transform(im, SC_ORIENTATION_FLIP_180);
|
||||
}
|
||||
} else if (im->kp && !paused) {
|
||||
// forward repeated events
|
||||
action_volume_up(im, action);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
break;
|
||||
case SDLK_LEFT:
|
||||
if (video && !repeat && down) {
|
||||
if (shift) {
|
||||
apply_orientation_transform(im,
|
||||
SC_ORIENTATION_FLIP_0);
|
||||
apply_orientation_transform(im, SC_ORIENTATION_FLIP_0);
|
||||
} else {
|
||||
apply_orientation_transform(im,
|
||||
SC_ORIENTATION_270);
|
||||
apply_orientation_transform(im, SC_ORIENTATION_270);
|
||||
}
|
||||
}
|
||||
return;
|
||||
case SDLK_RIGHT:
|
||||
if (video && !repeat && down) {
|
||||
if (shift) {
|
||||
apply_orientation_transform(im,
|
||||
SC_ORIENTATION_FLIP_0);
|
||||
apply_orientation_transform(im, SC_ORIENTATION_FLIP_0);
|
||||
} else {
|
||||
apply_orientation_transform(im,
|
||||
SC_ORIENTATION_90);
|
||||
apply_orientation_transform(im, SC_ORIENTATION_90);
|
||||
}
|
||||
}
|
||||
return;
|
||||
case SDLK_c:
|
||||
if (im->kp && !shift && !repeat && down && !paused) {
|
||||
get_device_clipboard(im, SC_COPY_KEY_COPY);
|
||||
}
|
||||
return;
|
||||
case SDLK_x:
|
||||
if (im->kp && !shift && !repeat && down && !paused) {
|
||||
get_device_clipboard(im, SC_COPY_KEY_CUT);
|
||||
}
|
||||
return;
|
||||
case SDLK_v:
|
||||
if (im->kp && !repeat && down && !paused) {
|
||||
if (shift || im->legacy_paste) {
|
||||
// inject the text as input events
|
||||
clipboard_paste(im);
|
||||
} else {
|
||||
// store the text in the device clipboard and paste,
|
||||
// without requesting an acknowledgment
|
||||
set_device_clipboard(im, true, SC_SEQUENCE_INVALID);
|
||||
}
|
||||
}
|
||||
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:
|
||||
if (control && !repeat && down && !paused) {
|
||||
if (shift) {
|
||||
collapse_panels(im);
|
||||
} else if (im->key_repeat == 0) {
|
||||
expand_notification_panel(im);
|
||||
} else {
|
||||
expand_settings_panel(im);
|
||||
}
|
||||
}
|
||||
return;
|
||||
case SDLK_r:
|
||||
if (control && !repeat && down && !paused) {
|
||||
if (shift) {
|
||||
}
|
||||
|
||||
// Flatten conditions to avoid additional indentation levels
|
||||
if (control) {
|
||||
// Controls for all sources
|
||||
switch (sdl_keycode) {
|
||||
case SDLK_R:
|
||||
if (!repeat && shift && down && !paused) {
|
||||
reset_video(im);
|
||||
} else {
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (control && !im->camera) {
|
||||
switch (sdl_keycode) {
|
||||
case SDLK_H:
|
||||
if (im->kp && !shift && !repeat && !paused) {
|
||||
action_home(im, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_B: // fall-through
|
||||
case SDLK_BACKSPACE:
|
||||
if (im->kp && !shift && !repeat && !paused) {
|
||||
action_back(im, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_S:
|
||||
if (im->kp && !shift && !repeat && !paused) {
|
||||
action_app_switch(im, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_M:
|
||||
if (im->kp && !shift && !repeat && !paused) {
|
||||
action_menu(im, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_P:
|
||||
if (im->kp && !shift && !repeat && !paused) {
|
||||
action_power(im, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_O:
|
||||
if (control && !repeat && down && !paused) {
|
||||
bool on = shift;
|
||||
set_display_power(im, on);
|
||||
}
|
||||
return;
|
||||
case SDLK_DOWN:
|
||||
if (im->kp && !shift && !paused) {
|
||||
// forward repeated events
|
||||
action_volume_down(im, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_UP:
|
||||
if (im->kp && !shift && !paused) {
|
||||
// forward repeated events
|
||||
action_volume_up(im, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_C:
|
||||
if (im->kp && !shift && !repeat && down && !paused) {
|
||||
get_device_clipboard(im, SC_COPY_KEY_COPY);
|
||||
}
|
||||
return;
|
||||
case SDLK_X:
|
||||
if (im->kp && !shift && !repeat && down && !paused) {
|
||||
get_device_clipboard(im, SC_COPY_KEY_CUT);
|
||||
}
|
||||
return;
|
||||
case SDLK_V:
|
||||
if (im->kp && !repeat && down && !paused) {
|
||||
if (shift || im->legacy_paste) {
|
||||
// inject the text as input events
|
||||
clipboard_paste(im);
|
||||
} else {
|
||||
// store the text in the device clipboard and paste,
|
||||
// without requesting an acknowledgment
|
||||
set_device_clipboard(im, true, SC_SEQUENCE_INVALID);
|
||||
}
|
||||
}
|
||||
return;
|
||||
case SDLK_N:
|
||||
if (!repeat && down && !paused) {
|
||||
if (shift) {
|
||||
collapse_panels(im);
|
||||
} else if (im->key_repeat == 0) {
|
||||
expand_notification_panel(im);
|
||||
} else {
|
||||
expand_settings_panel(im);
|
||||
}
|
||||
}
|
||||
return;
|
||||
case SDLK_R:
|
||||
if (!repeat && !shift && down && !paused) {
|
||||
rotate_device(im);
|
||||
}
|
||||
}
|
||||
return;
|
||||
case SDLK_k:
|
||||
if (control && !shift && !repeat && down && !paused
|
||||
&& im->kp && im->kp->hid) {
|
||||
// Only if the current keyboard is hid
|
||||
open_hard_keyboard_settings(im);
|
||||
}
|
||||
return;
|
||||
return;
|
||||
case SDLK_K:
|
||||
if (!shift && !repeat && down && !paused
|
||||
&& im->kp && im->kp->hid) {
|
||||
// Only if the current keyboard is hid
|
||||
open_hard_keyboard_settings(im);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (control && im->camera) {
|
||||
switch (sdl_keycode) {
|
||||
case SDLK_T:
|
||||
if (!repeat && down) {
|
||||
camera_set_torch(im, !shift);
|
||||
}
|
||||
return;
|
||||
case SDLK_DOWN:
|
||||
if (!shift && down && !paused) {
|
||||
// forward repeated events
|
||||
camera_zoom_out(im);
|
||||
}
|
||||
return;
|
||||
case SDLK_UP:
|
||||
if (!shift && down && !paused) {
|
||||
// forward repeated events
|
||||
camera_zoom_in(im);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -561,8 +645,10 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
return;
|
||||
}
|
||||
|
||||
assert(!im->camera);
|
||||
|
||||
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 +681,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 +691,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);
|
||||
@@ -632,6 +718,10 @@ sc_input_manager_get_position(struct sc_input_manager *im, int32_t x,
|
||||
static void
|
||||
sc_input_manager_process_mouse_motion(struct sc_input_manager *im,
|
||||
const SDL_MouseMotionEvent *event) {
|
||||
if (im->camera || !im->mp || im->screen->paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event->which == SDL_TOUCH_MOUSEID) {
|
||||
// simulated from touch events, so it's a duplicate
|
||||
return;
|
||||
@@ -667,18 +757,21 @@ sc_input_manager_process_mouse_motion(struct sc_input_manager *im,
|
||||
static void
|
||||
sc_input_manager_process_touch(struct sc_input_manager *im,
|
||||
const SDL_TouchFingerEvent *event) {
|
||||
if (im->camera || !im->mp || im->screen->paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!im->mp->ops->process_touch) {
|
||||
// The mouse processor does not support touch events
|
||||
return;
|
||||
}
|
||||
|
||||
int dw;
|
||||
int dh;
|
||||
SDL_GL_GetDrawableSize(im->screen->window, &dw, &dh);
|
||||
struct sc_size drawable_size =
|
||||
sc_sdl_get_window_size_in_pixels(im->screen->window);
|
||||
|
||||
// SDL touch event coordinates are normalized in the range [0; 1]
|
||||
int32_t x = event->x * dw;
|
||||
int32_t y = event->y * dh;
|
||||
int32_t x = event->x * (int32_t) drawable_size.width;
|
||||
int32_t y = event->y * (int32_t) drawable_size.height;
|
||||
|
||||
struct sc_touch_event evt = {
|
||||
.position = {
|
||||
@@ -687,7 +780,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,
|
||||
};
|
||||
|
||||
@@ -716,6 +809,13 @@ sc_input_manager_get_binding(const struct sc_mouse_binding_set *bindings,
|
||||
static void
|
||||
sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
||||
const SDL_MouseButtonEvent *event) {
|
||||
// some mouse events do not interact with the device, so process the event
|
||||
// even if control is disabled
|
||||
|
||||
if (im->camera) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event->which == SDL_TOUCH_MOUSEID) {
|
||||
// simulated from touch events, so it's a duplicate
|
||||
return;
|
||||
@@ -723,7 +823,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 +836,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;
|
||||
@@ -790,7 +890,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
||||
int32_t x = event->x;
|
||||
int32_t y = event->y;
|
||||
sc_screen_hidpi_scale_coords(im->screen, &x, &y);
|
||||
SDL_Rect *r = &im->screen->rect;
|
||||
SDL_FRect *r = &im->screen->rect;
|
||||
bool outside = x < r->x || x >= r->x + r->w
|
||||
|| y < r->y || y >= r->y + r->h;
|
||||
if (outside) {
|
||||
@@ -883,26 +983,25 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
||||
static void
|
||||
sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
|
||||
const SDL_MouseWheelEvent *event) {
|
||||
if (im->camera || !im->kp || im->screen->paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!im->mp->ops->process_mouse_scroll) {
|
||||
// The mouse processor does not support scroll events
|
||||
return;
|
||||
}
|
||||
|
||||
// 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 +1010,37 @@ 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);
|
||||
if (!gc) {
|
||||
LOGW("Could not open game controller");
|
||||
const SDL_GamepadDeviceEvent *event) {
|
||||
// Handle device added or removed even if paused
|
||||
|
||||
if (im->camera || !im->gp) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event->type == SDL_EVENT_GAMEPAD_ADDED) {
|
||||
SDL_Gamepad *sdl_gamepad = SDL_OpenGamepad(event->which);
|
||||
if (!sdl_gamepad) {
|
||||
LOGW("Could not open gamepad");
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_Joystick *joystick = SDL_GameControllerGetJoystick(gc);
|
||||
SDL_Joystick *joystick = SDL_GetGamepadJoystick(sdl_gamepad);
|
||||
if (!joystick) {
|
||||
LOGW("Could not get controller joystick");
|
||||
SDL_GameControllerClose(gc);
|
||||
LOGW("Could not get gamepad joystick");
|
||||
SDL_CloseGamepad(sdl_gamepad);
|
||||
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);
|
||||
if (gc) {
|
||||
SDL_GameControllerClose(gc);
|
||||
SDL_Gamepad *sdl_gamepad = SDL_GetGamepadFromID(id);
|
||||
if (sdl_gamepad) {
|
||||
SDL_CloseGamepad(sdl_gamepad);
|
||||
} else {
|
||||
LOGW("Unknown gamepad device removed");
|
||||
}
|
||||
@@ -952,7 +1057,11 @@ 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) {
|
||||
if (im->camera || !im->gp || im->screen->paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
enum sc_gamepad_axis axis = sc_gamepad_axis_from_sdl(event->axis);
|
||||
if (axis == SC_GAMEPAD_AXIS_UNKNOWN) {
|
||||
return;
|
||||
@@ -968,7 +1077,11 @@ 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) {
|
||||
if (im->camera || !im->gp || im->screen->paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
enum sc_gamepad_button button = sc_gamepad_button_from_sdl(event->button);
|
||||
if (button == SC_GAMEPAD_BUTTON_UNKNOWN) {
|
||||
return;
|
||||
@@ -976,7 +1089,7 @@ sc_input_manager_process_gamepad_button(struct sc_input_manager *im,
|
||||
|
||||
struct sc_gamepad_button_event evt = {
|
||||
.gamepad_id = event->which,
|
||||
.action = sc_action_from_sdl_controllerbutton_type(event->type),
|
||||
.action = sc_action_from_sdl_gamepad_button_type(event->type),
|
||||
.button = button,
|
||||
};
|
||||
im->gp->ops->process_gamepad_button(im->gp, &evt);
|
||||
@@ -991,8 +1104,12 @@ 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);
|
||||
if (im->camera || !im->controller) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(event->type == SDL_EVENT_DROP_FILE);
|
||||
char *file = strdup(event->data);
|
||||
if (!file) {
|
||||
LOG_OOM();
|
||||
return;
|
||||
@@ -1013,73 +1130,42 @@ sc_input_manager_process_file(struct sc_input_manager *im,
|
||||
void
|
||||
sc_input_manager_handle_event(struct sc_input_manager *im,
|
||||
const SDL_Event *event) {
|
||||
bool control = im->controller;
|
||||
bool paused = im->screen->paused;
|
||||
switch (event->type) {
|
||||
case SDL_TEXTINPUT:
|
||||
if (!im->kp || paused) {
|
||||
break;
|
||||
}
|
||||
case SDL_EVENT_TEXT_INPUT:
|
||||
sc_input_manager_process_text_input(im, &event->text);
|
||||
break;
|
||||
case SDL_KEYDOWN:
|
||||
case SDL_KEYUP:
|
||||
// some key events do not interact with the device, so process the
|
||||
// event even if control is disabled
|
||||
case SDL_EVENT_KEY_DOWN:
|
||||
case SDL_EVENT_KEY_UP:
|
||||
sc_input_manager_process_key(im, &event->key);
|
||||
break;
|
||||
case SDL_MOUSEMOTION:
|
||||
if (!im->mp || paused) {
|
||||
break;
|
||||
}
|
||||
case SDL_EVENT_MOUSE_MOTION:
|
||||
sc_input_manager_process_mouse_motion(im, &event->motion);
|
||||
break;
|
||||
case SDL_MOUSEWHEEL:
|
||||
if (!im->mp || paused) {
|
||||
break;
|
||||
}
|
||||
case SDL_EVENT_MOUSE_WHEEL:
|
||||
sc_input_manager_process_mouse_wheel(im, &event->wheel);
|
||||
break;
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
// some mouse events do not interact with the device, so process
|
||||
// the event even if control is disabled
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||
case SDL_EVENT_MOUSE_BUTTON_UP:
|
||||
sc_input_manager_process_mouse_button(im, &event->button);
|
||||
break;
|
||||
case SDL_FINGERMOTION:
|
||||
case SDL_FINGERDOWN:
|
||||
case SDL_FINGERUP:
|
||||
if (!im->mp || paused) {
|
||||
break;
|
||||
}
|
||||
case SDL_EVENT_FINGER_MOTION:
|
||||
case SDL_EVENT_FINGER_DOWN:
|
||||
case SDL_EVENT_FINGER_UP:
|
||||
sc_input_manager_process_touch(im, &event->tfinger);
|
||||
break;
|
||||
case SDL_CONTROLLERDEVICEADDED:
|
||||
case SDL_CONTROLLERDEVICEREMOVED:
|
||||
// Handle device added or removed even if paused
|
||||
if (!im->gp) {
|
||||
break;
|
||||
}
|
||||
sc_input_manager_process_gamepad_device(im, &event->cdevice);
|
||||
case SDL_EVENT_GAMEPAD_ADDED:
|
||||
case SDL_EVENT_GAMEPAD_REMOVED:
|
||||
sc_input_manager_process_gamepad_device(im, &event->gdevice);
|
||||
break;
|
||||
case SDL_CONTROLLERAXISMOTION:
|
||||
if (!im->gp || paused) {
|
||||
break;
|
||||
}
|
||||
sc_input_manager_process_gamepad_axis(im, &event->caxis);
|
||||
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
|
||||
sc_input_manager_process_gamepad_axis(im, &event->gaxis);
|
||||
break;
|
||||
case SDL_CONTROLLERBUTTONDOWN:
|
||||
case SDL_CONTROLLERBUTTONUP:
|
||||
if (!im->gp || paused) {
|
||||
break;
|
||||
}
|
||||
sc_input_manager_process_gamepad_button(im, &event->cbutton);
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_UP:
|
||||
sc_input_manager_process_gamepad_button(im, &event->gbutton);
|
||||
break;
|
||||
case SDL_DROPFILE: {
|
||||
if (!control) {
|
||||
break;
|
||||
}
|
||||
case SDL_EVENT_DROP_FILE:
|
||||
sc_input_manager_process_file(im, &event->drop);
|
||||
}
|
||||
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"
|
||||
@@ -24,6 +24,8 @@ struct sc_input_manager {
|
||||
struct sc_mouse_processor *mp;
|
||||
struct sc_gamepad_processor *gp;
|
||||
|
||||
bool camera;
|
||||
|
||||
struct sc_mouse_bindings mouse_bindings;
|
||||
bool legacy_paste;
|
||||
bool clipboard_autosync;
|
||||
@@ -53,6 +55,7 @@ struct sc_input_manager_params {
|
||||
struct sc_key_processor *kp;
|
||||
struct sc_mouse_processor *mp;
|
||||
struct sc_gamepad_processor *gp;
|
||||
bool camera;
|
||||
|
||||
struct sc_mouse_bindings mouse_bindings;
|
||||
bool legacy_paste;
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
#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>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "cli.h"
|
||||
#include "options.h"
|
||||
#include "scrcpy.h"
|
||||
#include "usb/scrcpy_otg.h"
|
||||
#ifdef HAVE_USB
|
||||
# include "usb/scrcpy_otg.h"
|
||||
#endif
|
||||
#include "util/log.h"
|
||||
#include "util/net.h"
|
||||
#include "util/thread.h"
|
||||
@@ -37,7 +39,7 @@ main_scrcpy(int argc, char *argv[]) {
|
||||
.opts = scrcpy_options_default,
|
||||
.help = false,
|
||||
.version = false,
|
||||
.pause_on_exit = SC_PAUSE_ON_EXIT_FALSE,
|
||||
.pause_on_exit = SC_PAUSE_ON_EXIT_UNDEFINED,
|
||||
};
|
||||
|
||||
#ifndef NDEBUG
|
||||
|
||||
@@ -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
|
||||
// The mouse will be captured on SDL_EVENT_MOUSE_BUTTON_UP, 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);
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ const struct scrcpy_options scrcpy_options_default = {
|
||||
.camera_id = NULL,
|
||||
.camera_size = NULL,
|
||||
.camera_ar = NULL,
|
||||
.camera_zoom = NULL,
|
||||
.camera_fps = 0,
|
||||
.log_level = SC_LOG_LEVEL_INFO,
|
||||
.video_codec = SC_CODEC_H264,
|
||||
@@ -56,6 +57,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,
|
||||
@@ -112,6 +114,7 @@ const struct scrcpy_options scrcpy_options_default = {
|
||||
.angle = NULL,
|
||||
.vd_destroy_content = true,
|
||||
.vd_system_decorations = true,
|
||||
.camera_torch = false,
|
||||
};
|
||||
|
||||
enum sc_orientation
|
||||
|
||||
@@ -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));
|
||||
@@ -226,6 +241,7 @@ struct scrcpy_options {
|
||||
const char *camera_id;
|
||||
const char *camera_size;
|
||||
const char *camera_ar;
|
||||
const char *camera_zoom;
|
||||
uint16_t camera_fps;
|
||||
enum sc_log_level log_level;
|
||||
enum sc_codec video_codec;
|
||||
@@ -251,6 +267,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;
|
||||
@@ -311,6 +328,7 @@ struct scrcpy_options {
|
||||
const char *start_app;
|
||||
bool vd_destroy_content;
|
||||
bool vd_system_decorations;
|
||||
bool camera_torch;
|
||||
};
|
||||
|
||||
extern const struct scrcpy_options scrcpy_options_default;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -444,7 +444,7 @@ sc_recorder_process_packets(struct sc_recorder *recorder) {
|
||||
int ret = av_write_trailer(recorder->ctx);
|
||||
if (ret < 0) {
|
||||
LOGE("Failed to write trailer to %s", recorder->filename);
|
||||
error = false;
|
||||
error = true;
|
||||
}
|
||||
|
||||
end:
|
||||
@@ -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,8 +199,8 @@ event_loop(struct scrcpy *s) {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (!sc_screen_handle_event(&s->screen, &event)) {
|
||||
return SCRCPY_EXIT_FAILURE;
|
||||
if (has_screen) {
|
||||
sc_screen_handle_event(&s->screen, &event);
|
||||
}
|
||||
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;
|
||||
}
|
||||
@@ -353,14 +355,21 @@ scrcpy_generate_scid(void) {
|
||||
|
||||
static void
|
||||
init_sdl_gamepads(void) {
|
||||
// Trigger a SDL_CONTROLLERDEVICEADDED event for all gamepads already
|
||||
// Trigger a SDL_EVENT_GAMEPAD_ADDED 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,
|
||||
@@ -459,6 +469,8 @@ scrcpy(struct scrcpy_options *options) {
|
||||
.power_on = options->power_on,
|
||||
.kill_adb_on_close = options->kill_adb_on_close,
|
||||
.camera_high_speed = options->camera_high_speed,
|
||||
.camera_torch = options->camera_torch,
|
||||
.camera_zoom = options->camera_zoom,
|
||||
.vd_destroy_content = options->vd_destroy_content,
|
||||
.vd_system_decorations = options->vd_system_decorations,
|
||||
.list = options->list,
|
||||
@@ -501,7 +513,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 +525,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;
|
||||
}
|
||||
@@ -552,7 +564,7 @@ scrcpy(struct scrcpy_options *options) {
|
||||
|
||||
struct sc_file_pusher *fp = NULL;
|
||||
|
||||
if (options->video_playback && options->control) {
|
||||
if (options->window && options->control) {
|
||||
if (!sc_file_pusher_init(&s->file_pusher, serial,
|
||||
options->push_target)) {
|
||||
goto end;
|
||||
@@ -792,6 +804,7 @@ aoa_complete:
|
||||
|
||||
struct sc_screen_params screen_params = {
|
||||
.video = options->video_playback,
|
||||
.camera = options->video_source == SC_VIDEO_SOURCE_CAMERA,
|
||||
.controller = controller,
|
||||
.fp = fp,
|
||||
.kp = kp,
|
||||
@@ -932,11 +945,11 @@ aoa_complete:
|
||||
}
|
||||
}
|
||||
|
||||
ret = event_loop(s);
|
||||
ret = event_loop(s, options->window);
|
||||
terminate_event_loop();
|
||||
LOGD("quit...");
|
||||
|
||||
if (options->video_playback) {
|
||||
if (options->window) {
|
||||
// Close the window immediately on closing, because screen_destroy()
|
||||
// may only be called once the video demuxer thread is joined (it may
|
||||
// take time)
|
||||
|
||||
566
app/src/screen.c
566
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/sdl.h"
|
||||
|
||||
#define DISPLAY_MARGINS 96
|
||||
|
||||
@@ -26,45 +27,25 @@ get_oriented_size(struct sc_size size, enum sc_orientation orientation) {
|
||||
return oriented_size;
|
||||
}
|
||||
|
||||
// get the window size in a struct sc_size
|
||||
static struct sc_size
|
||||
get_window_size(const struct sc_screen *screen) {
|
||||
int width;
|
||||
int height;
|
||||
SDL_GetWindowSize(screen->window, &width, &height);
|
||||
|
||||
struct sc_size size;
|
||||
size.width = width;
|
||||
size.height = height;
|
||||
return size;
|
||||
}
|
||||
|
||||
static struct sc_point
|
||||
get_window_position(const struct sc_screen *screen) {
|
||||
int x;
|
||||
int y;
|
||||
SDL_GetWindowPosition(screen->window, &x, &y);
|
||||
|
||||
struct sc_point point;
|
||||
point.x = x;
|
||||
point.y = y;
|
||||
return point;
|
||||
}
|
||||
|
||||
// set the window size to be applied when fullscreen is disabled
|
||||
static void
|
||||
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);
|
||||
static inline bool
|
||||
is_windowed(struct sc_screen *screen) {
|
||||
return !(SDL_GetWindowFlags(screen->window) & (SDL_WINDOW_FULLSCREEN
|
||||
| SDL_WINDOW_MINIMIZED
|
||||
| SDL_WINDOW_MAXIMIZED));
|
||||
}
|
||||
|
||||
// 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();
|
||||
if (!display) {
|
||||
LOGW("Could not get primary display: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = SDL_GetDisplayUsableBounds(display, &rect);
|
||||
if (!ok) {
|
||||
LOGW("Could not get display usable bounds: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
@@ -163,69 +144,113 @@ sc_screen_is_relative_mode(struct sc_screen *screen) {
|
||||
}
|
||||
|
||||
static void
|
||||
sc_screen_update_content_rect(struct sc_screen *screen) {
|
||||
assert(screen->video);
|
||||
|
||||
int dw;
|
||||
int dh;
|
||||
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
|
||||
|
||||
struct sc_size content_size = screen->content_size;
|
||||
// The drawable size is the window size * the HiDPI scale
|
||||
struct sc_size drawable_size = {dw, dh};
|
||||
|
||||
SDL_Rect *rect = &screen->rect;
|
||||
|
||||
if (is_optimal_size(drawable_size, content_size)) {
|
||||
compute_content_rect(struct sc_size render_size, struct sc_size content_size,
|
||||
bool can_upscale, SDL_FRect *rect) {
|
||||
if (is_optimal_size(render_size, content_size)) {
|
||||
rect->x = 0;
|
||||
rect->y = 0;
|
||||
rect->w = drawable_size.width;
|
||||
rect->h = drawable_size.height;
|
||||
rect->w = render_size.width;
|
||||
rect->h = render_size.height;
|
||||
return;
|
||||
}
|
||||
|
||||
bool keep_width = content_size.width * drawable_size.height
|
||||
> content_size.height * drawable_size.width;
|
||||
if (!can_upscale && content_size.width <= render_size.width
|
||||
&& content_size.height <= render_size.height) {
|
||||
// Center without upscaling
|
||||
rect->x = (render_size.width - content_size.width) / 2.f;
|
||||
rect->y = (render_size.height - content_size.height) / 2.f;
|
||||
rect->w = content_size.width;
|
||||
rect->h = content_size.height;
|
||||
return;
|
||||
}
|
||||
|
||||
bool keep_width = content_size.width * render_size.height
|
||||
> content_size.height * render_size.width;
|
||||
if (keep_width) {
|
||||
rect->x = 0;
|
||||
rect->w = drawable_size.width;
|
||||
rect->h = drawable_size.width * content_size.height
|
||||
/ content_size.width;
|
||||
rect->y = (drawable_size.height - rect->h) / 2;
|
||||
rect->w = render_size.width;
|
||||
rect->h = (float) render_size.width * content_size.height
|
||||
/ content_size.width;
|
||||
rect->y = (render_size.height - rect->h) / 2.f;
|
||||
} else {
|
||||
rect->y = 0;
|
||||
rect->h = drawable_size.height;
|
||||
rect->w = drawable_size.height * content_size.width
|
||||
/ content_size.height;
|
||||
rect->x = (drawable_size.width - rect->w) / 2;
|
||||
rect->h = render_size.height;
|
||||
rect->w = (float) render_size.height * content_size.width
|
||||
/ content_size.height;
|
||||
rect->x = (render_size.width - rect->w) / 2.f;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sc_screen_update_content_rect(struct sc_screen *screen) {
|
||||
// Only upscale video frames, not icon
|
||||
bool can_upscale = screen->video;
|
||||
|
||||
struct sc_size render_size =
|
||||
sc_sdl_get_render_output_size(screen->renderer);
|
||||
compute_content_rect(render_size, screen->content_size, can_upscale,
|
||||
&screen->rect);
|
||||
}
|
||||
|
||||
// render the texture to the renderer
|
||||
//
|
||||
// Set the update_content_rect flag if the window or content size may have
|
||||
// changed, so that the content rectangle is recomputed
|
||||
static void
|
||||
sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
|
||||
assert(screen->video);
|
||||
assert(!screen->video || screen->has_video_window);
|
||||
|
||||
if (update_content_rect) {
|
||||
sc_screen_update_content_rect(screen);
|
||||
}
|
||||
|
||||
enum sc_display_result res =
|
||||
sc_display_render(&screen->display, &screen->rect, screen->orientation);
|
||||
(void) res; // any error already logged
|
||||
SDL_Renderer *renderer = screen->renderer;
|
||||
sc_sdl_render_clear(renderer);
|
||||
|
||||
bool ok = false;
|
||||
SDL_Texture *texture = screen->tex.texture;
|
||||
if (!texture) {
|
||||
LOGW("No texture to render");
|
||||
goto end;
|
||||
}
|
||||
|
||||
SDL_FRect *geometry = &screen->rect;
|
||||
enum sc_orientation orientation = screen->orientation;
|
||||
|
||||
if (orientation == SC_ORIENTATION_0) {
|
||||
ok = SDL_RenderTexture(renderer, texture, NULL, geometry);
|
||||
} else {
|
||||
unsigned cw_rotation = sc_orientation_get_rotation(orientation);
|
||||
double angle = 90 * cw_rotation;
|
||||
|
||||
const SDL_FRect *dstrect = NULL;
|
||||
SDL_FRect rect;
|
||||
if (sc_orientation_is_swap(orientation)) {
|
||||
rect.x = geometry->x + (geometry->w - geometry->h) / 2.f;
|
||||
rect.y = geometry->y + (geometry->h - geometry->w) / 2.f;
|
||||
rect.w = geometry->h;
|
||||
rect.h = geometry->w;
|
||||
dstrect = ▭
|
||||
} else {
|
||||
dstrect = geometry;
|
||||
}
|
||||
|
||||
SDL_FlipMode flip = sc_orientation_is_mirror(orientation)
|
||||
? SDL_FLIP_HORIZONTAL : 0;
|
||||
|
||||
ok = SDL_RenderTextureRotated(renderer, texture, NULL, dstrect, angle,
|
||||
NULL, flip);
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
LOGE("Could not render texture: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
end:
|
||||
sc_sdl_render_present(renderer);
|
||||
}
|
||||
|
||||
static void
|
||||
sc_screen_render_novideo(struct sc_screen *screen) {
|
||||
enum sc_display_result res =
|
||||
sc_display_render(&screen->display, NULL, SC_ORIENTATION_0);
|
||||
(void) res; // any error already logged
|
||||
}
|
||||
|
||||
#if defined(__APPLE__) || defined(__WINDOWS__)
|
||||
#if defined(__APPLE__) || defined(_WIN32)
|
||||
# define CONTINUOUS_RESIZING_WORKAROUND
|
||||
#endif
|
||||
|
||||
@@ -235,28 +260,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_RESIZED) {
|
||||
// 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 +292,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,14 +342,13 @@ sc_screen_init(struct sc_screen *screen,
|
||||
const struct sc_screen_params *params) {
|
||||
screen->resize_pending = false;
|
||||
screen->has_frame = false;
|
||||
screen->fullscreen = false;
|
||||
screen->maximized = false;
|
||||
screen->minimized = false;
|
||||
screen->has_video_window = false;
|
||||
screen->paused = false;
|
||||
screen->resume_frame = NULL;
|
||||
screen->orientation = SC_ORIENTATION_0;
|
||||
|
||||
screen->video = params->video;
|
||||
screen->camera = params->camera;
|
||||
|
||||
screen->req.x = params->window_x;
|
||||
screen->req.y = params->window_y;
|
||||
@@ -360,7 +374,8 @@ sc_screen_init(struct sc_screen *screen,
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t window_flags = SDL_WINDOW_ALLOW_HIGHDPI;
|
||||
// Always create the window hidden to prevent blinking during initialization
|
||||
uint32_t window_flags = SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WINDOW_HIDDEN;
|
||||
if (params->always_on_top) {
|
||||
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
|
||||
}
|
||||
@@ -369,8 +384,7 @@ sc_screen_init(struct sc_screen *screen,
|
||||
}
|
||||
if (params->video) {
|
||||
// The window will be shown on first frame
|
||||
window_flags |= SDL_WINDOW_HIDDEN
|
||||
| SDL_WINDOW_RESIZABLE;
|
||||
window_flags |= SDL_WINDOW_RESIZABLE;
|
||||
}
|
||||
|
||||
const char *title = params->window_title;
|
||||
@@ -394,39 +408,86 @@ 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_sdl_create_window(title, x, y, width, height, window_flags);
|
||||
if (!screen->window) {
|
||||
LOGE("Could not create window: %s", SDL_GetError());
|
||||
goto error_destroy_fps_counter;
|
||||
}
|
||||
|
||||
SDL_Surface *icon = scrcpy_icon_load();
|
||||
if (icon) {
|
||||
SDL_SetWindowIcon(screen->window, icon);
|
||||
} 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;
|
||||
screen->renderer = SDL_CreateRenderer(screen->window, NULL);
|
||||
if (!screen->renderer) {
|
||||
LOGE("Could not create renderer: %s", SDL_GetError());
|
||||
goto error_destroy_window;
|
||||
}
|
||||
|
||||
SDL_Surface *icon_novideo = params->video ? NULL : icon;
|
||||
bool mipmaps = params->video && params->mipmaps;
|
||||
ok = sc_display_init(&screen->display, screen->window, icon_novideo,
|
||||
mipmaps);
|
||||
if (icon) {
|
||||
scrcpy_icon_destroy(icon);
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
screen->gl_context = NULL;
|
||||
|
||||
// starts with "opengl"
|
||||
const char *renderer_name = SDL_GetRendererName(screen->renderer);
|
||||
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
|
||||
if (use_opengl) {
|
||||
// Persuade macOS to give us something better than OpenGL 2.1.
|
||||
// If we create a Core Profile context, we get the best OpenGL version.
|
||||
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");
|
||||
screen->gl_context = SDL_GL_CreateContext(screen->window);
|
||||
if (!screen->gl_context) {
|
||||
LOGE("Could not create OpenGL context: %s", SDL_GetError());
|
||||
goto error_destroy_renderer;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool mipmaps = params->video;
|
||||
ok = sc_texture_init(&screen->tex, screen->renderer, mipmaps);
|
||||
if (!ok) {
|
||||
goto error_destroy_window;
|
||||
goto error_destroy_renderer;
|
||||
}
|
||||
|
||||
ok = SDL_StartTextInput(screen->window);
|
||||
if (!ok) {
|
||||
LOGE("Could not enable text input: %s", SDL_GetError());
|
||||
goto error_destroy_texture;
|
||||
}
|
||||
|
||||
SDL_Surface *icon = scrcpy_icon_load();
|
||||
if (icon) {
|
||||
if (!SDL_SetWindowIcon(screen->window, icon)) {
|
||||
LOGW("Could not set window icon: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
if (!params->video) {
|
||||
screen->content_size.width = icon->w;
|
||||
screen->content_size.height = icon->h;
|
||||
ok = sc_texture_set_from_surface(&screen->tex, icon);
|
||||
if (!ok) {
|
||||
LOGE("Could not set icon: %s", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
scrcpy_icon_destroy(icon);
|
||||
} else {
|
||||
// not fatal
|
||||
LOGE("Could not load icon");
|
||||
|
||||
if (!params->video) {
|
||||
// Make sure the content size is initialized
|
||||
screen->content_size.width = 256;
|
||||
screen->content_size.height = 256;
|
||||
}
|
||||
}
|
||||
|
||||
screen->frame = av_frame_alloc();
|
||||
if (!screen->frame) {
|
||||
LOG_OOM();
|
||||
goto error_destroy_display;
|
||||
goto error_destroy_texture;
|
||||
}
|
||||
|
||||
struct sc_input_manager_params im_params = {
|
||||
@@ -436,6 +497,7 @@ sc_screen_init(struct sc_screen *screen,
|
||||
.kp = params->kp,
|
||||
.mp = params->mp,
|
||||
.gp = params->gp,
|
||||
.camera = params->camera,
|
||||
.mouse_bindings = params->mouse_bindings,
|
||||
.legacy_paste = params->legacy_paste,
|
||||
.clipboard_autosync = params->clipboard_autosync,
|
||||
@@ -449,7 +511,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
|
||||
|
||||
@@ -465,15 +531,27 @@ sc_screen_init(struct sc_screen *screen,
|
||||
screen->open = false;
|
||||
#endif
|
||||
|
||||
if (!screen->video && sc_screen_is_relative_mode(screen)) {
|
||||
// Capture mouse immediately if video mirroring is disabled
|
||||
sc_mouse_capture_set_active(&screen->mc, true);
|
||||
if (!screen->video) {
|
||||
// Show the window immediately
|
||||
sc_sdl_show_window(screen->window);
|
||||
|
||||
if (sc_screen_is_relative_mode(screen)) {
|
||||
// Capture mouse immediately if video mirroring is disabled
|
||||
sc_mouse_capture_set_active(&screen->mc, true);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
error_destroy_display:
|
||||
sc_display_destroy(&screen->display);
|
||||
error_destroy_texture:
|
||||
sc_texture_destroy(&screen->tex);
|
||||
error_destroy_renderer:
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
if (screen->gl_context) {
|
||||
SDL_GL_DestroyContext(screen->gl_context);
|
||||
}
|
||||
#endif
|
||||
SDL_DestroyRenderer(screen->renderer);
|
||||
error_destroy_window:
|
||||
SDL_DestroyWindow(screen->window);
|
||||
error_destroy_fps_counter:
|
||||
@@ -490,13 +568,18 @@ sc_screen_show_initial_window(struct sc_screen *screen) {
|
||||
? screen->req.x : (int) SDL_WINDOWPOS_CENTERED;
|
||||
int y = screen->req.y != SC_WINDOW_POSITION_UNDEFINED
|
||||
? screen->req.y : (int) SDL_WINDOWPOS_CENTERED;
|
||||
struct sc_point position = {
|
||||
.x = x,
|
||||
.y = y,
|
||||
};
|
||||
|
||||
struct sc_size window_size =
|
||||
get_initial_optimal_size(screen->content_size, screen->req.width,
|
||||
screen->req.height);
|
||||
|
||||
set_window_size(screen, window_size);
|
||||
SDL_SetWindowPosition(screen->window, x, y);
|
||||
assert(is_windowed(screen));
|
||||
sc_sdl_set_window_size(screen->window, window_size);
|
||||
sc_sdl_set_window_position(screen->window, position);
|
||||
|
||||
if (screen->req.fullscreen) {
|
||||
sc_screen_toggle_fullscreen(screen);
|
||||
@@ -506,13 +589,13 @@ sc_screen_show_initial_window(struct sc_screen *screen) {
|
||||
sc_fps_counter_start(&screen->fps_counter);
|
||||
}
|
||||
|
||||
SDL_ShowWindow(screen->window);
|
||||
sc_sdl_show_window(screen->window);
|
||||
sc_screen_update_content_rect(screen);
|
||||
}
|
||||
|
||||
void
|
||||
sc_screen_hide_window(struct sc_screen *screen) {
|
||||
SDL_HideWindow(screen->window);
|
||||
sc_sdl_hide_window(screen->window);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -530,8 +613,12 @@ sc_screen_destroy(struct sc_screen *screen) {
|
||||
#ifndef NDEBUG
|
||||
assert(!screen->open);
|
||||
#endif
|
||||
sc_display_destroy(&screen->display);
|
||||
sc_texture_destroy(&screen->tex);
|
||||
av_frame_free(&screen->frame);
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
SDL_GL_DestroyContext(screen->gl_context);
|
||||
#endif
|
||||
SDL_DestroyRenderer(screen->renderer);
|
||||
SDL_DestroyWindow(screen->window);
|
||||
sc_fps_counter_destroy(&screen->fps_counter);
|
||||
sc_frame_buffer_destroy(&screen->fb);
|
||||
@@ -542,7 +629,7 @@ resize_for_content(struct sc_screen *screen, struct sc_size old_content_size,
|
||||
struct sc_size new_content_size) {
|
||||
assert(screen->video);
|
||||
|
||||
struct sc_size window_size = get_window_size(screen);
|
||||
struct sc_size window_size = sc_sdl_get_window_size(screen->window);
|
||||
struct sc_size target_size = {
|
||||
.width = (uint32_t) window_size.width * new_content_size.width
|
||||
/ old_content_size.width,
|
||||
@@ -550,14 +637,15 @@ resize_for_content(struct sc_screen *screen, struct sc_size old_content_size,
|
||||
/ old_content_size.height,
|
||||
};
|
||||
target_size = get_optimal_size(target_size, new_content_size, true);
|
||||
set_window_size(screen, target_size);
|
||||
assert(is_windowed(screen));
|
||||
sc_sdl_set_window_size(screen->window, target_size);
|
||||
}
|
||||
|
||||
static void
|
||||
set_content_size(struct sc_screen *screen, struct sc_size new_content_size) {
|
||||
assert(screen->video);
|
||||
|
||||
if (!screen->fullscreen && !screen->maximized && !screen->minimized) {
|
||||
if (is_windowed(screen)) {
|
||||
resize_for_content(screen, screen->content_size, new_content_size);
|
||||
} else if (!screen->resize_pending) {
|
||||
// Store the windowed size to be able to compute the optimal size once
|
||||
@@ -573,9 +661,7 @@ static void
|
||||
apply_pending_resize(struct sc_screen *screen) {
|
||||
assert(screen->video);
|
||||
|
||||
assert(!screen->fullscreen);
|
||||
assert(!screen->maximized);
|
||||
assert(!screen->minimized);
|
||||
assert(is_windowed(screen));
|
||||
if (screen->resize_pending) {
|
||||
resize_for_content(screen, screen->windowed_content_size,
|
||||
screen->content_size);
|
||||
@@ -603,44 +689,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,26 +697,34 @@ 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 (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->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;
|
||||
}
|
||||
}
|
||||
|
||||
res = sc_display_update_texture(&screen->display, frame);
|
||||
if (res == SC_DISPLAY_RESULT_ERROR) {
|
||||
bool ok = sc_texture_set_from_frame(&screen->tex, frame);
|
||||
if (!ok) {
|
||||
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);
|
||||
|
||||
@@ -720,7 +776,10 @@ sc_screen_set_paused(struct sc_screen *screen, bool paused) {
|
||||
av_frame_free(&screen->frame);
|
||||
screen->frame = screen->resume_frame;
|
||||
screen->resume_frame = NULL;
|
||||
sc_screen_apply_frame(screen);
|
||||
bool ok = sc_screen_apply_frame(screen);
|
||||
if (!ok) {
|
||||
LOGE("Resume frame update failed");
|
||||
}
|
||||
}
|
||||
|
||||
if (!paused) {
|
||||
@@ -738,31 +797,28 @@ 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 req_fullscreen =
|
||||
!(SDL_GetWindowFlags(screen->window) & SDL_WINDOW_FULLSCREEN);
|
||||
|
||||
bool ok = SDL_SetWindowFullscreen(screen->window, req_fullscreen);
|
||||
if (!ok) {
|
||||
LOGW("Could not switch fullscreen mode: %s", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
|
||||
screen->fullscreen = !screen->fullscreen;
|
||||
if (!screen->fullscreen && !screen->maximized && !screen->minimized) {
|
||||
apply_pending_resize(screen);
|
||||
}
|
||||
|
||||
LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed");
|
||||
sc_screen_render(screen, true);
|
||||
LOGD("Requested %s mode", req_fullscreen ? "fullscreen" : "windowed");
|
||||
}
|
||||
|
||||
void
|
||||
sc_screen_resize_to_fit(struct sc_screen *screen) {
|
||||
assert(screen->video);
|
||||
|
||||
if (screen->fullscreen || screen->maximized || screen->minimized) {
|
||||
if (!is_windowed(screen)) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct sc_point point = get_window_position(screen);
|
||||
struct sc_size window_size = get_window_size(screen);
|
||||
struct sc_point point = sc_sdl_get_window_position(screen->window);
|
||||
struct sc_size window_size = sc_sdl_get_window_size(screen->window);
|
||||
|
||||
struct sc_size optimal_size =
|
||||
get_optimal_size(window_size, screen->content_size, false);
|
||||
@@ -770,11 +826,14 @@ sc_screen_resize_to_fit(struct sc_screen *screen) {
|
||||
// Center the window related to the device screen
|
||||
assert(optimal_size.width <= window_size.width);
|
||||
assert(optimal_size.height <= window_size.height);
|
||||
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);
|
||||
struct sc_point new_position = {
|
||||
.x = point.x + (window_size.width - optimal_size.width) / 2,
|
||||
.y = point.y + (window_size.height - optimal_size.height) / 2,
|
||||
};
|
||||
|
||||
sc_sdl_set_window_size(screen->window, optimal_size);
|
||||
sc_sdl_set_window_position(screen->window, new_position);
|
||||
LOGD("Resized to optimal size: %ux%u", optimal_size.width,
|
||||
optimal_size.height);
|
||||
}
|
||||
@@ -783,92 +842,65 @@ void
|
||||
sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) {
|
||||
assert(screen->video);
|
||||
|
||||
if (screen->fullscreen || screen->minimized) {
|
||||
if (!is_windowed(screen)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (screen->maximized) {
|
||||
SDL_RestoreWindow(screen->window);
|
||||
screen->maximized = false;
|
||||
}
|
||||
|
||||
struct sc_size content_size = screen->content_size;
|
||||
SDL_SetWindowSize(screen->window, content_size.width, content_size.height);
|
||||
sc_sdl_set_window_size(screen->window, content_size);
|
||||
LOGD("Resized to pixel-perfect: %ux%u", content_size.width,
|
||||
content_size.height);
|
||||
}
|
||||
|
||||
bool
|
||||
void
|
||||
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) {
|
||||
LOGE("Frame update failed\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
case SDL_WINDOWEVENT:
|
||||
if (!screen->video
|
||||
&& event->window.event == SDL_WINDOWEVENT_EXPOSED) {
|
||||
sc_screen_render_novideo(screen);
|
||||
case SDL_EVENT_WINDOW_EXPOSED:
|
||||
if (!screen->video || 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;
|
||||
return;
|
||||
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
|
||||
if (screen->has_video_window) {
|
||||
sc_screen_render(screen, 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;
|
||||
return;
|
||||
case SDL_EVENT_WINDOW_RESTORED:
|
||||
if (screen->has_video_window && is_windowed(screen)) {
|
||||
apply_pending_resize(screen);
|
||||
sc_screen_render(screen, true);
|
||||
}
|
||||
return true;
|
||||
return;
|
||||
case SDL_EVENT_WINDOW_ENTER_FULLSCREEN:
|
||||
LOGD("Switched to fullscreen mode");
|
||||
assert(screen->has_video_window);
|
||||
return;
|
||||
case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN:
|
||||
LOGD("Switched to windowed mode");
|
||||
assert(screen->has_video_window);
|
||||
if (is_windowed(screen)) {
|
||||
apply_pending_resize(screen);
|
||||
sc_screen_render(screen, true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (sc_screen_is_relative_mode(screen)
|
||||
&& sc_mouse_capture_handle_event(&screen->mc, event)) {
|
||||
// The mouse capture handler consumed the event
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
sc_input_manager_handle_event(&screen->im, event);
|
||||
return true;
|
||||
}
|
||||
|
||||
struct sc_point
|
||||
@@ -937,9 +969,15 @@ 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) {
|
||||
// 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);
|
||||
|
||||
struct sc_size window_size = sc_sdl_get_window_size(screen->window);
|
||||
int64_t ww = window_size.width;
|
||||
int64_t wh = window_size.height;
|
||||
|
||||
struct sc_size drawable_size =
|
||||
sc_sdl_get_window_size_in_pixels(screen->window);
|
||||
int64_t dw = drawable_size.width;
|
||||
int64_t dh = drawable_size.height;
|
||||
|
||||
// scale for HiDPI (64 bits for intermediate multiplications)
|
||||
*x = (int64_t) *x * dw / ww;
|
||||
|
||||
@@ -5,23 +5,27 @@
|
||||
|
||||
#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>
|
||||
|
||||
#include "controller.h"
|
||||
#include "coords.h"
|
||||
#include "display.h"
|
||||
#include "fps_counter.h"
|
||||
#include "frame_buffer.h"
|
||||
#include "input_manager.h"
|
||||
#include "mouse_capture.h"
|
||||
#include "options.h"
|
||||
#include "texture.h"
|
||||
#include "trait/key_processor.h"
|
||||
#include "trait/frame_sink.h"
|
||||
#include "trait/mouse_processor.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
# define SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
#endif
|
||||
|
||||
struct sc_screen {
|
||||
struct sc_frame_sink frame_sink; // frame sink trait
|
||||
|
||||
@@ -30,8 +34,9 @@ struct sc_screen {
|
||||
#endif
|
||||
|
||||
bool video;
|
||||
bool camera;
|
||||
|
||||
struct sc_display display;
|
||||
struct sc_texture tex;
|
||||
struct sc_input_manager im;
|
||||
struct sc_mouse_capture mc; // only used in mouse relative mode
|
||||
struct sc_frame_buffer fb;
|
||||
@@ -48,6 +53,11 @@ struct sc_screen {
|
||||
} req;
|
||||
|
||||
SDL_Window *window;
|
||||
SDL_Renderer *renderer;
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
SDL_GLContext gl_context;
|
||||
#endif
|
||||
|
||||
struct sc_size frame_size;
|
||||
struct sc_size content_size; // rotated frame_size
|
||||
|
||||
@@ -59,11 +69,9 @@ struct sc_screen {
|
||||
// client orientation
|
||||
enum sc_orientation orientation;
|
||||
// rectangle of the content (excluding black borders)
|
||||
struct SDL_Rect rect;
|
||||
struct SDL_FRect rect;
|
||||
bool has_frame;
|
||||
bool fullscreen;
|
||||
bool maximized;
|
||||
bool minimized;
|
||||
bool has_video_window;
|
||||
|
||||
AVFrame *frame;
|
||||
|
||||
@@ -73,6 +81,7 @@ struct sc_screen {
|
||||
|
||||
struct sc_screen_params {
|
||||
bool video;
|
||||
bool camera;
|
||||
|
||||
struct sc_controller *controller;
|
||||
struct sc_file_pusher *fp;
|
||||
@@ -148,8 +157,7 @@ void
|
||||
sc_screen_set_paused(struct sc_screen *screen, bool paused);
|
||||
|
||||
// react to SDL events
|
||||
// If this function returns false, scrcpy must exit with an error.
|
||||
bool
|
||||
void
|
||||
sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event);
|
||||
|
||||
// convert point from window coordinates to frame coordinates
|
||||
|
||||
@@ -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
|
||||
@@ -326,6 +357,13 @@ execute_server(struct sc_server *server,
|
||||
if (params->camera_high_speed) {
|
||||
ADD_PARAM("camera_high_speed=true");
|
||||
}
|
||||
if (params->camera_torch) {
|
||||
ADD_PARAM("camera_torch=true");
|
||||
}
|
||||
if (params->camera_zoom) {
|
||||
VALIDATE_STRING(params->camera_zoom);
|
||||
ADD_PARAM("camera_zoom=%s", params->camera_zoom);
|
||||
}
|
||||
if (params->show_touches) {
|
||||
ADD_PARAM("show_touches=true");
|
||||
}
|
||||
@@ -376,6 +414,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");
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ struct sc_server_params {
|
||||
const char *camera_id;
|
||||
const char *camera_size;
|
||||
const char *camera_ar;
|
||||
const char *camera_zoom;
|
||||
uint16_t camera_fps;
|
||||
struct sc_port_range port_range;
|
||||
uint32_t tunnel_host;
|
||||
@@ -50,6 +51,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;
|
||||
@@ -67,6 +69,7 @@ struct sc_server_params {
|
||||
bool power_on;
|
||||
bool kill_adb_on_close;
|
||||
bool camera_high_speed;
|
||||
bool camera_torch;
|
||||
bool vd_destroy_content;
|
||||
bool vd_system_decorations;
|
||||
uint8_t list;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,26 +3,14 @@
|
||||
#include <processthreadsapi.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "util/command.h"
|
||||
#include "util/log.h"
|
||||
#include "util/str.h"
|
||||
|
||||
#define CMD_MAX_LEN 8192
|
||||
|
||||
static bool
|
||||
build_cmd(char *cmd, size_t len, const char *const argv[]) {
|
||||
// Windows command-line parsing is WTF:
|
||||
// <http://daviddeley.com/autohotkey/parameters/parameters.htm#WINPASS>
|
||||
// only make it work for this very specific program
|
||||
// (don't handle escaping nor quotes)
|
||||
size_t ret = sc_str_join(cmd, argv, ' ', len);
|
||||
if (ret >= len) {
|
||||
LOGE("Command too long (%" SC_PRIsizet " chars)", len - 1);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
enum sc_process_result
|
||||
sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned flags,
|
||||
HANDLE *pin, HANDLE *pout, HANDLE *perr) {
|
||||
@@ -137,8 +125,9 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned flags,
|
||||
si.lpAttributeList = lpAttributeList;
|
||||
}
|
||||
|
||||
char *cmd = malloc(CMD_MAX_LEN);
|
||||
if (!cmd || !build_cmd(cmd, CMD_MAX_LEN, argv)) {
|
||||
assert(argv && *argv);
|
||||
char *cmd = sc_command_serialize_windows(argv);
|
||||
if (!cmd) {
|
||||
LOG_OOM();
|
||||
goto error_free_attribute_list;
|
||||
}
|
||||
|
||||
227
app/src/texture.c
Normal file
227
app/src/texture.c
Normal file
@@ -0,0 +1,227 @@
|
||||
#include "texture.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include <libavutil/pixfmt.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
bool
|
||||
sc_texture_init(struct sc_texture *tex, SDL_Renderer *renderer, bool mipmaps) {
|
||||
const char *renderer_name = SDL_GetRendererName(renderer);
|
||||
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
|
||||
|
||||
tex->mipmaps = false;
|
||||
|
||||
// starts with "opengl"
|
||||
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
|
||||
if (use_opengl) {
|
||||
struct sc_opengl *gl = &tex->gl;
|
||||
sc_opengl_init(gl);
|
||||
|
||||
LOGI("OpenGL version: %s", gl->version);
|
||||
|
||||
if (mipmaps) {
|
||||
bool supports_mipmaps =
|
||||
sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */
|
||||
2, 0 /* OpenGL ES 2.0+ */);
|
||||
if (supports_mipmaps) {
|
||||
LOGI("Trilinear filtering enabled");
|
||||
tex->mipmaps = true;
|
||||
} else {
|
||||
LOGW("Trilinear filtering disabled "
|
||||
"(OpenGL 3.0+ or ES 2.0+ required)");
|
||||
}
|
||||
} else {
|
||||
LOGI("Trilinear filtering disabled");
|
||||
}
|
||||
} else if (mipmaps) {
|
||||
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
|
||||
}
|
||||
|
||||
tex->renderer = renderer;
|
||||
tex->texture = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
sc_texture_destroy(struct sc_texture *tex) {
|
||||
if (tex->texture) {
|
||||
SDL_DestroyTexture(tex->texture);
|
||||
}
|
||||
}
|
||||
|
||||
static enum SDL_Colorspace
|
||||
sc_texture_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_texture_create_frame_texture(struct sc_texture *tex,
|
||||
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_texture_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 = tex->renderer;
|
||||
SDL_Texture *texture = SDL_CreateTextureWithProperties(renderer, props);
|
||||
SDL_DestroyProperties(props);
|
||||
if (!texture) {
|
||||
LOGD("Could not create texture: %s", SDL_GetError());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (tex->mipmaps) {
|
||||
struct sc_opengl *gl = &tex->gl;
|
||||
|
||||
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(tex->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
|
||||
tex->texture_id = texture_id;
|
||||
gl->BindTexture(GL_TEXTURE_2D, tex->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);
|
||||
|
||||
gl->BindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_texture_set_from_frame(struct sc_texture *tex, const AVFrame *frame) {
|
||||
|
||||
struct sc_size size = {frame->width, frame->height};
|
||||
assert(size.width && size.height);
|
||||
|
||||
if (!tex->texture
|
||||
|| tex->texture_type != SC_TEXTURE_TYPE_FRAME
|
||||
|| tex->texture_size.width != size.width
|
||||
|| tex->texture_size.height != size.height) {
|
||||
// Incompatible texture, recreate it
|
||||
enum AVColorSpace color_space = frame->colorspace;
|
||||
enum AVColorRange color_range = frame->color_range;
|
||||
|
||||
if (tex->texture) {
|
||||
SDL_DestroyTexture(tex->texture);
|
||||
}
|
||||
|
||||
tex->texture = sc_texture_create_frame_texture(tex, size, color_space,
|
||||
color_range);
|
||||
if (!tex->texture) {
|
||||
return false;
|
||||
}
|
||||
|
||||
tex->texture_size = size;
|
||||
tex->texture_type = SC_TEXTURE_TYPE_FRAME;
|
||||
|
||||
LOGI("Texture: %" PRIu16 "x%" PRIu16, size.width, size.height);
|
||||
}
|
||||
|
||||
assert(tex->texture);
|
||||
assert(tex->texture_type == SC_TEXTURE_TYPE_FRAME);
|
||||
|
||||
bool ok = SDL_UpdateYUVTexture(tex->texture, NULL,
|
||||
frame->data[0], frame->linesize[0],
|
||||
frame->data[1], frame->linesize[1],
|
||||
frame->data[2], frame->linesize[2]);
|
||||
if (!ok) {
|
||||
LOGD("Could not update texture: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tex->mipmaps) {
|
||||
assert(tex->texture_id);
|
||||
struct sc_opengl *gl = &tex->gl;
|
||||
|
||||
gl->BindTexture(GL_TEXTURE_2D, tex->texture_id);
|
||||
gl->GenerateMipmap(GL_TEXTURE_2D);
|
||||
gl->BindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_texture_set_from_surface(struct sc_texture *tex, SDL_Surface *surface) {
|
||||
if (tex->texture) {
|
||||
SDL_DestroyTexture(tex->texture);
|
||||
}
|
||||
|
||||
tex->texture = SDL_CreateTextureFromSurface(tex->renderer, surface);
|
||||
if (!tex->texture) {
|
||||
LOGE("Could not create texture: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
tex->texture_size.width = surface->w;
|
||||
tex->texture_size.height = surface->h;
|
||||
tex->texture_type = SC_TEXTURE_TYPE_ICON;
|
||||
|
||||
return true;
|
||||
}
|
||||
44
app/src/texture.h
Normal file
44
app/src/texture.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#ifndef SC_DISPLAY_H
|
||||
#define SC_DISPLAY_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <libavutil/frame.h>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "coords.h"
|
||||
#include "opengl.h"
|
||||
|
||||
enum sc_texture_type {
|
||||
SC_TEXTURE_TYPE_FRAME,
|
||||
SC_TEXTURE_TYPE_ICON,
|
||||
};
|
||||
|
||||
struct sc_texture {
|
||||
SDL_Renderer *renderer; // owned by the caller
|
||||
SDL_Texture *texture;
|
||||
// Only valid if texture != NULL
|
||||
struct sc_size texture_size;
|
||||
enum sc_texture_type texture_type;
|
||||
|
||||
struct sc_opengl gl;
|
||||
|
||||
bool mipmaps;
|
||||
uint32_t texture_id; // only set if mipmaps is enabled
|
||||
};
|
||||
|
||||
bool
|
||||
sc_texture_init(struct sc_texture *tex, SDL_Renderer *renderer, bool mipmaps);
|
||||
|
||||
void
|
||||
sc_texture_destroy(struct sc_texture *tex);
|
||||
|
||||
bool
|
||||
sc_texture_set_from_frame(struct sc_texture *tex, const AVFrame *frame);
|
||||
|
||||
bool
|
||||
sc_texture_set_from_surface(struct sc_texture *tex, SDL_Surface *surface);
|
||||
|
||||
#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,21 @@ 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->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,9 @@ sc_gamepad_processor_process_gamepad_added(struct sc_gamepad_processor *gp,
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_GameController* game_controller =
|
||||
SDL_GameControllerFromInstanceID(event->gamepad_id);
|
||||
assert(game_controller);
|
||||
const char *name = SDL_GameControllerName(game_controller);
|
||||
SDL_Gamepad *sdl_gamepad = SDL_GetGamepadFromID(event->gamepad_id);
|
||||
assert(sdl_gamepad);
|
||||
const char *name = SDL_GetGamepadName(sdl_gamepad);
|
||||
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,13 +3,13 @@
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
# include "adb/adb.h"
|
||||
#endif
|
||||
#include "events.h"
|
||||
#include "usb/screen_otg.h"
|
||||
#include "screen.h"
|
||||
#include "usb/aoa_hid.h"
|
||||
#include "usb/gamepad_aoa.h"
|
||||
#include "usb/keyboard_aoa.h"
|
||||
@@ -23,7 +23,7 @@ struct scrcpy_otg {
|
||||
struct sc_mouse_aoa mouse;
|
||||
struct sc_gamepad_aoa gamepad;
|
||||
|
||||
struct sc_screen_otg screen_otg;
|
||||
struct sc_screen screen;
|
||||
};
|
||||
|
||||
static void
|
||||
@@ -45,11 +45,11 @@ 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:
|
||||
sc_screen_otg_handle_event(&s->screen_otg, &event);
|
||||
sc_screen_handle_event(&s->screen, &event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -63,23 +63,19 @@ 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)) {
|
||||
LOGE("Could not initialize SDL controller: %s", SDL_GetError());
|
||||
if (!SDL_Init(SDL_INIT_GAMEPAD)) {
|
||||
LOGE("Could not initialize SDL gamepad: %s", SDL_GetError());
|
||||
// Not fatal, keyboard/mouse should still work
|
||||
}
|
||||
}
|
||||
@@ -92,13 +88,14 @@ scrcpy_otg(struct scrcpy_options *options) {
|
||||
|
||||
enum scrcpy_exit_code ret = SCRCPY_EXIT_FAILURE;
|
||||
|
||||
struct sc_keyboard_aoa *keyboard = NULL;
|
||||
struct sc_mouse_aoa *mouse = NULL;
|
||||
struct sc_gamepad_aoa *gamepad = NULL;
|
||||
struct sc_key_processor *kp = NULL;
|
||||
struct sc_mouse_processor *mp = NULL;
|
||||
struct sc_gamepad_processor *gp = NULL;
|
||||
bool usb_device_initialized = false;
|
||||
bool usb_connected = false;
|
||||
bool aoa_started = false;
|
||||
bool aoa_initialized = false;
|
||||
bool screen_initialized = false;
|
||||
|
||||
#ifdef _WIN32
|
||||
// On Windows, only one process could open a USB device
|
||||
@@ -161,7 +158,7 @@ scrcpy_otg(struct scrcpy_options *options) {
|
||||
if (!ok) {
|
||||
goto end;
|
||||
}
|
||||
keyboard = &s->keyboard;
|
||||
kp = &s->keyboard.key_processor;
|
||||
}
|
||||
|
||||
if (enable_mouse) {
|
||||
@@ -169,12 +166,12 @@ scrcpy_otg(struct scrcpy_options *options) {
|
||||
if (!ok) {
|
||||
goto end;
|
||||
}
|
||||
mouse = &s->mouse;
|
||||
mp = &s->mouse.mouse_processor;
|
||||
}
|
||||
|
||||
if (enable_gamepad) {
|
||||
sc_gamepad_aoa_init(&s->gamepad, &s->aoa);
|
||||
gamepad = &s->gamepad;
|
||||
gp = &s->gamepad.gamepad_processor;
|
||||
}
|
||||
|
||||
ok = sc_aoa_start(&s->aoa);
|
||||
@@ -188,10 +185,18 @@ scrcpy_otg(struct scrcpy_options *options) {
|
||||
window_title = usb_device.product ? usb_device.product : "scrcpy";
|
||||
}
|
||||
|
||||
struct sc_screen_otg_params params = {
|
||||
.keyboard = keyboard,
|
||||
.mouse = mouse,
|
||||
.gamepad = gamepad,
|
||||
struct sc_screen_params params = {
|
||||
.video = false,
|
||||
.camera = false,
|
||||
.controller = false,
|
||||
.fp = NULL,
|
||||
.kp = kp,
|
||||
.mp = mp,
|
||||
.gp = gp,
|
||||
.mouse_bindings = options->mouse_bindings,
|
||||
.legacy_paste = false,
|
||||
.clipboard_autosync = false,
|
||||
.shortcut_mods = options->shortcut_mods,
|
||||
.window_title = window_title,
|
||||
.always_on_top = options->always_on_top,
|
||||
.window_x = options->window_x,
|
||||
@@ -199,13 +204,17 @@ scrcpy_otg(struct scrcpy_options *options) {
|
||||
.window_width = options->window_width,
|
||||
.window_height = options->window_height,
|
||||
.window_borderless = options->window_borderless,
|
||||
.shortcut_mods = options->shortcut_mods,
|
||||
.orientation = SC_ORIENTATION_0,
|
||||
.mipmaps = options->mipmaps,
|
||||
.fullscreen = false,
|
||||
.start_fps_counter = false,
|
||||
};
|
||||
|
||||
ok = sc_screen_otg_init(&s->screen_otg, ¶ms);
|
||||
ok = sc_screen_init(&s->screen, ¶ms);
|
||||
if (!ok) {
|
||||
goto end;
|
||||
}
|
||||
screen_initialized = true;
|
||||
|
||||
// usb_device not needed anymore
|
||||
sc_usb_device_destroy(&usb_device);
|
||||
@@ -220,13 +229,17 @@ end:
|
||||
}
|
||||
sc_usb_stop(&s->usb);
|
||||
|
||||
if (mouse) {
|
||||
if (screen_initialized) {
|
||||
sc_screen_interrupt(&s->screen);
|
||||
}
|
||||
|
||||
if (mp) {
|
||||
sc_mouse_aoa_destroy(&s->mouse);
|
||||
}
|
||||
if (keyboard) {
|
||||
if (kp) {
|
||||
sc_keyboard_aoa_destroy(&s->keyboard);
|
||||
}
|
||||
if (gamepad) {
|
||||
if (gp) {
|
||||
sc_gamepad_aoa_destroy(&s->gamepad);
|
||||
}
|
||||
|
||||
@@ -247,5 +260,10 @@ end:
|
||||
|
||||
sc_usb_destroy(&s->usb);
|
||||
|
||||
if (screen_initialized) {
|
||||
sc_screen_join(&s->screen);
|
||||
sc_screen_destroy(&s->screen);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1,319 +0,0 @@
|
||||
#include "screen_otg.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "icon.h"
|
||||
#include "options.h"
|
||||
#include "util/acksync.h"
|
||||
#include "util/log.h"
|
||||
|
||||
static void
|
||||
sc_screen_otg_render(struct sc_screen_otg *screen) {
|
||||
SDL_RenderClear(screen->renderer);
|
||||
if (screen->texture) {
|
||||
SDL_RenderCopy(screen->renderer, screen->texture, NULL, NULL);
|
||||
}
|
||||
SDL_RenderPresent(screen->renderer);
|
||||
}
|
||||
|
||||
bool
|
||||
sc_screen_otg_init(struct sc_screen_otg *screen,
|
||||
const struct sc_screen_otg_params *params) {
|
||||
screen->keyboard = params->keyboard;
|
||||
screen->mouse = params->mouse;
|
||||
screen->gamepad = params->gamepad;
|
||||
|
||||
const char *title = params->window_title;
|
||||
assert(title);
|
||||
|
||||
int x = params->window_x != SC_WINDOW_POSITION_UNDEFINED
|
||||
? params->window_x : (int) SDL_WINDOWPOS_UNDEFINED;
|
||||
int y = params->window_y != SC_WINDOW_POSITION_UNDEFINED
|
||||
? params->window_y : (int) SDL_WINDOWPOS_UNDEFINED;
|
||||
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;
|
||||
if (params->always_on_top) {
|
||||
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
|
||||
}
|
||||
if (params->window_borderless) {
|
||||
window_flags |= SDL_WINDOW_BORDERLESS;
|
||||
}
|
||||
|
||||
screen->window = SDL_CreateWindow(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);
|
||||
if (!screen->renderer) {
|
||||
LOGE("Could not create renderer: %s", SDL_GetError());
|
||||
goto error_destroy_window;
|
||||
}
|
||||
|
||||
SDL_Surface *icon = scrcpy_icon_load();
|
||||
|
||||
if (icon) {
|
||||
SDL_SetWindowIcon(screen->window, icon);
|
||||
|
||||
if (SDL_RenderSetLogicalSize(screen->renderer, icon->w, icon->h)) {
|
||||
LOGW("Could not set renderer logical size: %s", SDL_GetError());
|
||||
// don't fail
|
||||
}
|
||||
|
||||
screen->texture = SDL_CreateTextureFromSurface(screen->renderer, icon);
|
||||
scrcpy_icon_destroy(icon);
|
||||
if (!screen->texture) {
|
||||
goto error_destroy_renderer;
|
||||
}
|
||||
} else {
|
||||
screen->texture = NULL;
|
||||
LOGW("Could not load icon");
|
||||
}
|
||||
|
||||
sc_mouse_capture_init(&screen->mc, screen->window, params->shortcut_mods);
|
||||
|
||||
if (screen->mouse) {
|
||||
// Capture mouse on start
|
||||
sc_mouse_capture_set_active(&screen->mc, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
error_destroy_window:
|
||||
SDL_DestroyWindow(screen->window);
|
||||
error_destroy_renderer:
|
||||
SDL_DestroyRenderer(screen->renderer);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
sc_screen_otg_destroy(struct sc_screen_otg *screen) {
|
||||
if (screen->texture) {
|
||||
SDL_DestroyTexture(screen->texture);
|
||||
}
|
||||
SDL_DestroyRenderer(screen->renderer);
|
||||
SDL_DestroyWindow(screen->window);
|
||||
}
|
||||
|
||||
static void
|
||||
sc_screen_otg_process_key(struct sc_screen_otg *screen,
|
||||
const SDL_KeyboardEvent *event) {
|
||||
assert(screen->keyboard);
|
||||
struct sc_key_processor *kp = &screen->keyboard->key_processor;
|
||||
|
||||
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),
|
||||
.repeat = event->repeat,
|
||||
.mods_state = sc_mods_state_from_sdl(event->keysym.mod),
|
||||
};
|
||||
|
||||
assert(kp->ops->process_key);
|
||||
kp->ops->process_key(kp, &evt, SC_SEQUENCE_INVALID);
|
||||
}
|
||||
|
||||
static void
|
||||
sc_screen_otg_process_mouse_motion(struct sc_screen_otg *screen,
|
||||
const SDL_MouseMotionEvent *event) {
|
||||
assert(screen->mouse);
|
||||
struct sc_mouse_processor *mp = &screen->mouse->mouse_processor;
|
||||
|
||||
struct sc_mouse_motion_event evt = {
|
||||
// .position not used for HID events
|
||||
.xrel = event->xrel,
|
||||
.yrel = event->yrel,
|
||||
.buttons_state = sc_mouse_buttons_state_from_sdl(event->state),
|
||||
};
|
||||
|
||||
assert(mp->ops->process_mouse_motion);
|
||||
mp->ops->process_mouse_motion(mp, &evt);
|
||||
}
|
||||
|
||||
static void
|
||||
sc_screen_otg_process_mouse_button(struct sc_screen_otg *screen,
|
||||
const SDL_MouseButtonEvent *event) {
|
||||
assert(screen->mouse);
|
||||
struct sc_mouse_processor *mp = &screen->mouse->mouse_processor;
|
||||
|
||||
uint32_t sdl_buttons_state = SDL_GetMouseState(NULL, NULL);
|
||||
|
||||
struct sc_mouse_click_event evt = {
|
||||
// .position not used for HID events
|
||||
.action = sc_action_from_sdl_mousebutton_type(event->type),
|
||||
.button = sc_mouse_button_from_sdl(event->button),
|
||||
.buttons_state = sc_mouse_buttons_state_from_sdl(sdl_buttons_state),
|
||||
};
|
||||
|
||||
assert(mp->ops->process_mouse_click);
|
||||
mp->ops->process_mouse_click(mp, &evt);
|
||||
}
|
||||
|
||||
static void
|
||||
sc_screen_otg_process_mouse_wheel(struct sc_screen_otg *screen,
|
||||
const SDL_MouseWheelEvent *event) {
|
||||
assert(screen->mouse);
|
||||
struct sc_mouse_processor *mp = &screen->mouse->mouse_processor;
|
||||
|
||||
uint32_t sdl_buttons_state = SDL_GetMouseState(NULL, NULL);
|
||||
|
||||
struct sc_mouse_scroll_event evt = {
|
||||
// .position not used for HID events
|
||||
.hscroll = event->x,
|
||||
.vscroll = event->y,
|
||||
.buttons_state = sc_mouse_buttons_state_from_sdl(sdl_buttons_state),
|
||||
};
|
||||
|
||||
assert(mp->ops->process_mouse_scroll);
|
||||
mp->ops->process_mouse_scroll(mp, &evt);
|
||||
}
|
||||
|
||||
static void
|
||||
sc_screen_otg_process_gamepad_device(struct sc_screen_otg *screen,
|
||||
const SDL_ControllerDeviceEvent *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 (!gc) {
|
||||
LOGW("Could not open game controller");
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_Joystick *joystick = SDL_GameControllerGetJoystick(gc);
|
||||
if (!joystick) {
|
||||
LOGW("Could not get controller joystick");
|
||||
SDL_GameControllerClose(gc);
|
||||
return;
|
||||
}
|
||||
|
||||
struct sc_gamepad_device_event evt = {
|
||||
.gamepad_id = SDL_JoystickInstanceID(joystick),
|
||||
};
|
||||
gp->ops->process_gamepad_added(gp, &evt);
|
||||
} else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
|
||||
SDL_JoystickID id = event->which;
|
||||
|
||||
SDL_GameController *gc = SDL_GameControllerFromInstanceID(id);
|
||||
if (gc) {
|
||||
SDL_GameControllerClose(gc);
|
||||
} else {
|
||||
LOGW("Unknown gamepad device removed");
|
||||
}
|
||||
|
||||
struct sc_gamepad_device_event evt = {
|
||||
.gamepad_id = id,
|
||||
};
|
||||
gp->ops->process_gamepad_removed(gp, &evt);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sc_screen_otg_process_gamepad_axis(struct sc_screen_otg *screen,
|
||||
const SDL_ControllerAxisEvent *event) {
|
||||
assert(screen->gamepad);
|
||||
struct sc_gamepad_processor *gp = &screen->gamepad->gamepad_processor;
|
||||
|
||||
enum sc_gamepad_axis axis = sc_gamepad_axis_from_sdl(event->axis);
|
||||
if (axis == SC_GAMEPAD_AXIS_UNKNOWN) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct sc_gamepad_axis_event evt = {
|
||||
.gamepad_id = event->which,
|
||||
.axis = axis,
|
||||
.value = event->value,
|
||||
};
|
||||
gp->ops->process_gamepad_axis(gp, &evt);
|
||||
}
|
||||
|
||||
static void
|
||||
sc_screen_otg_process_gamepad_button(struct sc_screen_otg *screen,
|
||||
const SDL_ControllerButtonEvent *event) {
|
||||
assert(screen->gamepad);
|
||||
struct sc_gamepad_processor *gp = &screen->gamepad->gamepad_processor;
|
||||
|
||||
enum sc_gamepad_button button = sc_gamepad_button_from_sdl(event->button);
|
||||
if (button == SC_GAMEPAD_BUTTON_UNKNOWN) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct sc_gamepad_button_event evt = {
|
||||
.gamepad_id = event->which,
|
||||
.action = sc_action_from_sdl_controllerbutton_type(event->type),
|
||||
.button = button,
|
||||
};
|
||||
gp->ops->process_gamepad_button(gp, &evt);
|
||||
}
|
||||
|
||||
void
|
||||
sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event) {
|
||||
if (sc_mouse_capture_handle_event(&screen->mc, event)) {
|
||||
// The mouse capture handler consumed the event
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event->type) {
|
||||
case SDL_WINDOWEVENT:
|
||||
switch (event->window.event) {
|
||||
case SDL_WINDOWEVENT_EXPOSED:
|
||||
sc_screen_otg_render(screen);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
case SDL_KEYDOWN:
|
||||
if (screen->keyboard) {
|
||||
sc_screen_otg_process_key(screen, &event->key);
|
||||
}
|
||||
break;
|
||||
case SDL_KEYUP:
|
||||
if (screen->keyboard) {
|
||||
sc_screen_otg_process_key(screen, &event->key);
|
||||
}
|
||||
break;
|
||||
case SDL_MOUSEMOTION:
|
||||
if (screen->mouse) {
|
||||
sc_screen_otg_process_mouse_motion(screen, &event->motion);
|
||||
}
|
||||
break;
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
if (screen->mouse) {
|
||||
sc_screen_otg_process_mouse_button(screen, &event->button);
|
||||
}
|
||||
break;
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
if (screen->mouse) {
|
||||
sc_screen_otg_process_mouse_button(screen, &event->button);
|
||||
}
|
||||
break;
|
||||
case SDL_MOUSEWHEEL:
|
||||
if (screen->mouse) {
|
||||
sc_screen_otg_process_mouse_wheel(screen, &event->wheel);
|
||||
}
|
||||
break;
|
||||
case SDL_CONTROLLERDEVICEADDED:
|
||||
case SDL_CONTROLLERDEVICEREMOVED:
|
||||
// Handle device added or removed even if paused
|
||||
if (screen->gamepad) {
|
||||
sc_screen_otg_process_gamepad_device(screen, &event->cdevice);
|
||||
}
|
||||
break;
|
||||
case SDL_CONTROLLERAXISMOTION:
|
||||
if (screen->gamepad) {
|
||||
sc_screen_otg_process_gamepad_axis(screen, &event->caxis);
|
||||
}
|
||||
break;
|
||||
case SDL_CONTROLLERBUTTONDOWN:
|
||||
case SDL_CONTROLLERBUTTONUP:
|
||||
if (screen->gamepad) {
|
||||
sc_screen_otg_process_gamepad_button(screen, &event->cbutton);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
#ifndef SC_SCREEN_OTG_H
|
||||
#define SC_SCREEN_OTG_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include "mouse_capture.h"
|
||||
#include "usb/gamepad_aoa.h"
|
||||
#include "usb/keyboard_aoa.h"
|
||||
#include "usb/mouse_aoa.h"
|
||||
|
||||
struct sc_screen_otg {
|
||||
struct sc_keyboard_aoa *keyboard;
|
||||
struct sc_mouse_aoa *mouse;
|
||||
struct sc_gamepad_aoa *gamepad;
|
||||
|
||||
SDL_Window *window;
|
||||
SDL_Renderer *renderer;
|
||||
SDL_Texture *texture;
|
||||
|
||||
struct sc_mouse_capture mc;
|
||||
};
|
||||
|
||||
struct sc_screen_otg_params {
|
||||
struct sc_keyboard_aoa *keyboard;
|
||||
struct sc_mouse_aoa *mouse;
|
||||
struct sc_gamepad_aoa *gamepad;
|
||||
|
||||
const char *window_title;
|
||||
bool always_on_top;
|
||||
int16_t window_x; // accepts SC_WINDOW_POSITION_UNDEFINED
|
||||
int16_t window_y; // accepts SC_WINDOW_POSITION_UNDEFINED
|
||||
uint16_t window_width;
|
||||
uint16_t window_height;
|
||||
bool window_borderless;
|
||||
uint8_t shortcut_mods; // OR of enum sc_shortcut_mod values
|
||||
};
|
||||
|
||||
bool
|
||||
sc_screen_otg_init(struct sc_screen_otg *screen,
|
||||
const struct sc_screen_otg_params *params);
|
||||
|
||||
void
|
||||
sc_screen_otg_destroy(struct sc_screen_otg *screen);
|
||||
|
||||
void
|
||||
sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event);
|
||||
|
||||
#endif
|
||||
@@ -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);
|
||||
|
||||
98
app/src/util/command.c
Normal file
98
app/src/util/command.c
Normal file
@@ -0,0 +1,98 @@
|
||||
#include "command.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "util/strbuf.h"
|
||||
|
||||
char *
|
||||
sc_command_serialize_windows(const char *const argv[]) {
|
||||
// <https://learn.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments>
|
||||
// <https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw>
|
||||
|
||||
struct sc_strbuf buf;
|
||||
bool ok = sc_strbuf_init(&buf, 1024);
|
||||
if (!ok) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define BUF_PUSH(C) \
|
||||
do { \
|
||||
if (!sc_strbuf_append_char(&buf, C)) { \
|
||||
goto end; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
while (*argv) {
|
||||
const char *arg = *argv;
|
||||
|
||||
BUF_PUSH('"');
|
||||
|
||||
// <https://learn.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments>
|
||||
//
|
||||
// """
|
||||
// If an even number of backslashes is followed by a double quote mark,
|
||||
// then one backslash (\) is placed in the argv array for every pair of
|
||||
// backslashes (\\), and the double quote mark (") is interpreted as a
|
||||
// string delimiter.
|
||||
//
|
||||
// If an odd number of backslashes is followed by a double quote mark,
|
||||
// then one backslash (\) is placed in the argv array for every pair of
|
||||
// backslashes (\\). The double quote mark is interpreted as an escape
|
||||
// sequence by the remaining backslash, causing a literal double quote
|
||||
// mark (") to be placed in argv.
|
||||
// """
|
||||
//
|
||||
// To produce correct escaping according to what the parser will do, we
|
||||
// must count the number of successive backslashes.
|
||||
unsigned backslashes = 0;
|
||||
|
||||
for (const char *c = arg; *c; c++) {
|
||||
switch (*c) {
|
||||
case '"':
|
||||
while (backslashes) {
|
||||
// Double all backslashes before a quote
|
||||
BUF_PUSH('\\');
|
||||
BUF_PUSH('\\');
|
||||
--backslashes;
|
||||
}
|
||||
BUF_PUSH('\\');
|
||||
BUF_PUSH('"');
|
||||
backslashes = 0;
|
||||
break;
|
||||
case '\\':
|
||||
++backslashes;
|
||||
break;
|
||||
default:
|
||||
while (backslashes) {
|
||||
// Put all backslashes as literals
|
||||
BUF_PUSH('\\');
|
||||
--backslashes;
|
||||
}
|
||||
BUF_PUSH(*c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (backslashes) {
|
||||
// Double all backslashes before a quote
|
||||
BUF_PUSH('\\');
|
||||
BUF_PUSH('\\');
|
||||
--backslashes;
|
||||
}
|
||||
|
||||
BUF_PUSH('"');
|
||||
|
||||
++argv;
|
||||
|
||||
// Argument separator
|
||||
if (*argv) {
|
||||
BUF_PUSH(' ');
|
||||
}
|
||||
}
|
||||
|
||||
return buf.s;
|
||||
|
||||
end:
|
||||
free(buf.s);
|
||||
return NULL;
|
||||
}
|
||||
17
app/src/util/command.h
Normal file
17
app/src/util/command.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef SC_COMMAND_H
|
||||
#define SC_COMMAND_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
/**
|
||||
* Serialize an argv array for Windows
|
||||
*
|
||||
* Convert a NULL-terminated argument array into a single escaped string
|
||||
* suitable for massing to CreateProcess() on Windows.
|
||||
*
|
||||
* The returned value must be freed by the caller.
|
||||
*/
|
||||
char *
|
||||
sc_command_serialize_windows(const char *const argv[]);
|
||||
|
||||
#endif
|
||||
@@ -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,18 @@ 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);
|
||||
#ifdef _WIN32
|
||||
SetConsoleOutputCP(CP_UTF8);
|
||||
#endif
|
||||
|
||||
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>
|
||||
|
||||
168
app/src/util/sdl.c
Normal file
168
app/src/util/sdl.c
Normal file
@@ -0,0 +1,168 @@
|
||||
#include "sdl.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
SDL_Window *
|
||||
sc_sdl_create_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;
|
||||
}
|
||||
|
||||
struct sc_size
|
||||
sc_sdl_get_window_size(SDL_Window *window) {
|
||||
int width;
|
||||
int height;
|
||||
bool ok = SDL_GetWindowSize(window, &width, &height);
|
||||
if (!ok) {
|
||||
LOGE("Could not get window size: %s", SDL_GetError());
|
||||
LOGE("Please report the error");
|
||||
// fatal error
|
||||
abort();
|
||||
}
|
||||
|
||||
struct sc_size size = {
|
||||
.width = width,
|
||||
.height = height,
|
||||
};
|
||||
return size;
|
||||
}
|
||||
|
||||
struct sc_size
|
||||
sc_sdl_get_window_size_in_pixels(SDL_Window *window) {
|
||||
int width;
|
||||
int height;
|
||||
bool ok = SDL_GetWindowSizeInPixels(window, &width, &height);
|
||||
if (!ok) {
|
||||
LOGE("Could not get window size: %s", SDL_GetError());
|
||||
LOGE("Please report the error");
|
||||
// fatal error
|
||||
abort();
|
||||
}
|
||||
|
||||
struct sc_size size = {
|
||||
.width = width,
|
||||
.height = height,
|
||||
};
|
||||
return size;
|
||||
}
|
||||
|
||||
void
|
||||
sc_sdl_set_window_size(SDL_Window *window, struct sc_size size) {
|
||||
bool ok = SDL_SetWindowSize(window, size.width, size.height);
|
||||
if (!ok) {
|
||||
LOGE("Could not set window size: %s", SDL_GetError());
|
||||
assert(!"unexpected");
|
||||
}
|
||||
}
|
||||
|
||||
struct sc_point
|
||||
sc_sdl_get_window_position(SDL_Window *window) {
|
||||
int x;
|
||||
int y;
|
||||
bool ok = SDL_GetWindowPosition(window, &x, &y);
|
||||
if (!ok) {
|
||||
LOGE("Could not get window position: %s", SDL_GetError());
|
||||
LOGE("Please report the error");
|
||||
// fatal error
|
||||
abort();
|
||||
}
|
||||
|
||||
struct sc_point point = {
|
||||
.x = x,
|
||||
.y = y,
|
||||
};
|
||||
return point;
|
||||
}
|
||||
|
||||
void
|
||||
sc_sdl_set_window_position(SDL_Window *window, struct sc_point point) {
|
||||
bool ok = SDL_SetWindowPosition(window, point.x, point.y);
|
||||
if (!ok) {
|
||||
LOGE("Could not set window position: %s", SDL_GetError());
|
||||
assert(!"unexpected");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sc_sdl_show_window(SDL_Window *window) {
|
||||
bool ok = SDL_ShowWindow(window);
|
||||
if (!ok) {
|
||||
LOGE("Could not show window: %s", SDL_GetError());
|
||||
assert(!"unexpected");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sc_sdl_hide_window(SDL_Window *window) {
|
||||
bool ok = SDL_HideWindow(window);
|
||||
if (!ok) {
|
||||
LOGE("Could not hide window: %s", SDL_GetError());
|
||||
assert(!"unexpected");
|
||||
}
|
||||
}
|
||||
|
||||
struct sc_size
|
||||
sc_sdl_get_render_output_size(SDL_Renderer *renderer) {
|
||||
int width;
|
||||
int height;
|
||||
bool ok = SDL_GetRenderOutputSize(renderer, &width, &height);
|
||||
if (!ok) {
|
||||
LOGE("Could not get render output size: %s", SDL_GetError());
|
||||
LOGE("Please report the error");
|
||||
// fatal error
|
||||
abort();
|
||||
}
|
||||
|
||||
struct sc_size size = {
|
||||
.width = width,
|
||||
.height = height,
|
||||
};
|
||||
return size;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_sdl_render_clear(SDL_Renderer *renderer) {
|
||||
bool ok = SDL_RenderClear(renderer);
|
||||
if (!ok) {
|
||||
LOGW("Could not clear rendering: %s", SDL_GetError());
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
void
|
||||
sc_sdl_render_present(SDL_Renderer *renderer) {
|
||||
bool ok = SDL_RenderPresent(renderer);
|
||||
if (!ok) {
|
||||
LOGE("Could not render: %s", SDL_GetError());
|
||||
assert(!"unexpected");
|
||||
}
|
||||
}
|
||||
46
app/src/util/sdl.h
Normal file
46
app/src/util/sdl.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#ifndef SC_SDL_H
|
||||
#define SC_SDL_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <SDL3/SDL_render.h>
|
||||
#include <SDL3/SDL_video.h>
|
||||
|
||||
#include "coords.h"
|
||||
|
||||
SDL_Window *
|
||||
sc_sdl_create_window(const char *title, int64_t x, int64_t y, int64_t width,
|
||||
int64_t height, int64_t flags);
|
||||
|
||||
struct sc_size
|
||||
sc_sdl_get_window_size(SDL_Window *window);
|
||||
|
||||
struct sc_size
|
||||
sc_sdl_get_window_size_in_pixels(SDL_Window *window);
|
||||
|
||||
void
|
||||
sc_sdl_set_window_size(SDL_Window *window, struct sc_size size);
|
||||
|
||||
struct sc_point
|
||||
sc_sdl_get_window_position(SDL_Window *window);
|
||||
|
||||
void
|
||||
sc_sdl_set_window_position(SDL_Window *window, struct sc_point point);
|
||||
|
||||
void
|
||||
sc_sdl_show_window(SDL_Window *window);
|
||||
|
||||
void
|
||||
sc_sdl_hide_window(SDL_Window *window);
|
||||
|
||||
struct sc_size
|
||||
sc_sdl_get_render_output_size(SDL_Renderer *renderer);
|
||||
|
||||
bool
|
||||
sc_sdl_render_clear(SDL_Renderer *renderer);
|
||||
|
||||
void
|
||||
sc_sdl_render_present(SDL_Renderer *renderer);
|
||||
|
||||
#endif
|
||||
@@ -49,21 +49,6 @@ truncated:
|
||||
return n;
|
||||
}
|
||||
|
||||
char *
|
||||
sc_str_quote(const char *src) {
|
||||
size_t len = strlen(src);
|
||||
char *quoted = malloc(len + 3);
|
||||
if (!quoted) {
|
||||
LOG_OOM();
|
||||
return NULL;
|
||||
}
|
||||
memcpy("ed[1], src, len);
|
||||
quoted[0] = '"';
|
||||
quoted[len + 1] = '"';
|
||||
quoted[len + 2] = '\0';
|
||||
return quoted;
|
||||
}
|
||||
|
||||
char *
|
||||
sc_str_concat(const char *start, const char *end) {
|
||||
assert(start);
|
||||
|
||||
@@ -32,14 +32,6 @@ sc_strncpy(char *dest, const char *src, size_t n);
|
||||
size_t
|
||||
sc_str_join(char *dst, const char *const tokens[], char sep, size_t n);
|
||||
|
||||
/**
|
||||
* Quote a string
|
||||
*
|
||||
* Return a new allocated string, surrounded with quotes (`"`).
|
||||
*/
|
||||
char *
|
||||
sc_str_quote(const char *src);
|
||||
|
||||
/**
|
||||
* Concat two strings
|
||||
*
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -163,6 +163,45 @@ static void test_adb_devices_spaces(void) {
|
||||
sc_adb_devices_destroy(&vec);
|
||||
}
|
||||
|
||||
static void test_adb_devices_serial_with_spaces(void) {
|
||||
char output[] =
|
||||
"List of devices attached\n"
|
||||
"adb-0123456789AB-CDdefg (2)._adb-tls-connect._tcp device "
|
||||
"product:blazer model:Pixel_10_Pro device:blazer transport_id:3\n";
|
||||
|
||||
struct sc_vec_adb_devices vec = SC_VECTOR_INITIALIZER;
|
||||
bool ok = sc_adb_parse_devices(output, &vec);
|
||||
assert(ok);
|
||||
assert(vec.size == 1);
|
||||
|
||||
struct sc_adb_device *device = &vec.data[0];
|
||||
assert(!strcmp("adb-0123456789AB-CDdefg (2)._adb-tls-connect._tcp",
|
||||
device->serial));
|
||||
assert(!strcmp("device", device->state));
|
||||
assert(!strcmp("Pixel_10_Pro", device->model));
|
||||
|
||||
sc_adb_devices_destroy(&vec);
|
||||
}
|
||||
|
||||
static void test_adb_devices_with_devpath_without_colon(void) {
|
||||
char output[] =
|
||||
"List of devices attached\n"
|
||||
"12345678 device 3-1 product:manet model:23117RK66C "
|
||||
"device:manet transport_id:2\n";
|
||||
|
||||
struct sc_vec_adb_devices vec = SC_VECTOR_INITIALIZER;
|
||||
bool ok = sc_adb_parse_devices(output, &vec);
|
||||
assert(ok);
|
||||
assert(vec.size == 1);
|
||||
|
||||
struct sc_adb_device *device = &vec.data[0];
|
||||
assert(!strcmp("12345678", device->serial));
|
||||
assert(!strcmp("device", device->state));
|
||||
assert(!strcmp("23117RK66C", device->model));
|
||||
|
||||
sc_adb_devices_destroy(&vec);
|
||||
}
|
||||
|
||||
static void test_get_ip_single_line(void) {
|
||||
char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src "
|
||||
"192.168.12.34\r\r\n";
|
||||
@@ -265,6 +304,8 @@ int main(int argc, char *argv[]) {
|
||||
test_adb_devices_without_header();
|
||||
test_adb_devices_corrupted();
|
||||
test_adb_devices_spaces();
|
||||
test_adb_devices_serial_with_spaces();
|
||||
test_adb_devices_with_devpath_without_colon();
|
||||
|
||||
test_get_ip_single_line();
|
||||
test_get_ip_single_line_without_eol();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
61
app/tests/test_command_windows.c
Normal file
61
app/tests/test_command_windows.c
Normal file
@@ -0,0 +1,61 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "util/command.h"
|
||||
|
||||
static void test_command_with_spaces(void) {
|
||||
const char *const argv[] = {
|
||||
"C:\\Program Files\\scrcpy\\adb",
|
||||
"-s",
|
||||
"serial with spaces",
|
||||
"push",
|
||||
"E:\\some folder\\scrcpy-server",
|
||||
"/data/local/tmp/scrcpy-server.jar",
|
||||
NULL,
|
||||
};
|
||||
char *cmd = sc_command_serialize_windows(argv);
|
||||
const char *expected = "\"C:\\Program Files\\scrcpy\\adb\" "
|
||||
"\"-s\" "
|
||||
"\"serial with spaces\" "
|
||||
"\"push\" "
|
||||
"\"E:\\some folder\\scrcpy-server\" "
|
||||
"\"/data/local/tmp/scrcpy-server.jar\"";
|
||||
|
||||
assert(!strcmp(expected, cmd));
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
static void test_command_with_backslashes(void) {
|
||||
const char *const argv[] = {
|
||||
"a\\\\ b\\",
|
||||
"def \\",
|
||||
"gh\"i\" \\\\",
|
||||
"jkl\\\\",
|
||||
"mno\\",
|
||||
"p\\\"qr",
|
||||
NULL,
|
||||
};
|
||||
|
||||
char *cmd = sc_command_serialize_windows(argv);
|
||||
const char *expected = "\"a\\\\ b\\\\\" "
|
||||
"\"def \\\\\" "
|
||||
"\"gh\\\"i\\\" \\\\\\\\\" "
|
||||
"\"jkl\\\\\\\\\" "
|
||||
"\"mno\\\\\" "
|
||||
"\"p\\\\\\\"qr\"";
|
||||
|
||||
assert(!strcmp(expected, cmd));
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
|
||||
test_command_with_spaces();
|
||||
test_command_with_backslashes();
|
||||
|
||||
return 0;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user