Compare commits
131 Commits
mouse_bind
...
codec_mism
Author | SHA1 | Date | |
---|---|---|---|
e39adb1b79 | |||
665ccb32f5 | |||
292adf294d | |||
f9f3bfabe3 | |||
6d23a389ca | |||
337901368e | |||
4cc4abdcc8 | |||
befc0fac5b | |||
f01a622ead | |||
0ba430a462 | |||
91d40c7548 | |||
9f3d51106d | |||
bf2b679e70 | |||
7f250dd669 | |||
68e27c7357 | |||
c4febd55eb | |||
f9d1a333a0 | |||
64a25f6e9d | |||
5fe884276b | |||
3e68244dd3 | |||
a34a62ca4b | |||
a59c6df4b7 | |||
f4d1e49ad9 | |||
4565f36ee6 | |||
c8479fe8bf | |||
de8455400c | |||
1f5be743b4 | |||
222916eebe | |||
6c707ad8a3 | |||
d748ac75e6 | |||
6f0c9eba9b | |||
f6219d2640 | |||
6e9b0d7d4c | |||
3e9c89c535 | |||
9af3bacdd6 | |||
2dd02ebb80 | |||
dad04bf138 | |||
1afc8ca368 | |||
785099b74d | |||
08da2e068e | |||
49c8ca34fd | |||
a84b0dfd0c | |||
cbf5db85c1 | |||
72ee195693 | |||
8620d06741 | |||
e9240f6804 | |||
e9b32d8a52 | |||
ce4e1fc420 | |||
e8f02685e9 | |||
4a6b335f7d | |||
90ee0062cb | |||
e03888d587 | |||
8453e3ba7d | |||
145a9468fd | |||
1d713d7598 | |||
265a15e0b1 | |||
6451ad271a | |||
bec3321fff | |||
dea1fe3386 | |||
a7cae59578 | |||
f089ea67e1 | |||
63ced79842 | |||
33a8c39beb | |||
903a5aaaf5 | |||
21b412cd98 | |||
3b241af3f6 | |||
21e2e2606e | |||
0c95794463 | |||
523f939532 | |||
dd47cefa47 | |||
44b3fd82b1 | |||
cc41115625 | |||
773c23fda2 | |||
992b4922fe | |||
67f93350f6 | |||
52136268ef | |||
0a6ccdc4df | |||
5d2441d198 | |||
2b6089cbfc | |||
f691ebb1b4 | |||
071d459ad7 | |||
bbfac9ae1f | |||
65bd6bd8d4 | |||
ed4066902d | |||
127a271d34 | |||
31116a60d7 | |||
a10f8cd798 | |||
53c6eb66ea | |||
0f076083e8 | |||
053bf83f58 | |||
414ce4c754 | |||
a2f3a5cf18 | |||
5e605b9b8f | |||
cf09e78323 | |||
3b8ec0c38d | |||
39132ff2dd | |||
9d1d79b004 | |||
e0cdc2ace3 | |||
bbcd763612 | |||
c57a0512ba | |||
e84db2914d | |||
80ca7b15e5 | |||
79242957a0 | |||
fe7494c492 | |||
9989668226 | |||
6baea57987 | |||
86b8286217 | |||
51fee79bf5 | |||
6808288823 | |||
0bce4d7f56 | |||
6d98766cd5 | |||
487a6b9cf4 | |||
b50f9eb41d | |||
46041e0cc0 | |||
b419eef55e | |||
cc8e6133b0 | |||
126da0cb18 | |||
1d3b6dac69 | |||
a4f8c02502 | |||
a8871bfad7 | |||
89df38f641 | |||
c95e6964c5 | |||
343f715323 | |||
f13f00021f | |||
48c2c03093 | |||
1e3deabd6c | |||
7633228278 | |||
576e7552a2 | |||
c27ab46efb | |||
b5849db32f | |||
206809a99a |
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
github: [rom1v]
|
||||||
|
liberapay: rom1v
|
||||||
|
custom: ["https://paypal.me/rom2v"]
|
26
.github/ISSUE_TEMPLATE/bug_report.md
vendored
26
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -7,17 +7,25 @@ assignees: ''
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
- [ ] I have read the [FAQ](https://github.com/Genymobile/scrcpy/blob/master/FAQ.md).
|
_Please read the [prerequisites] to run scrcpy._
|
||||||
- [ ] I have searched in existing [issues](https://github.com/Genymobile/scrcpy/issues).
|
|
||||||
|
|
||||||
**Environment**
|
[prerequisites]: https://github.com/Genymobile/scrcpy#prerequisites
|
||||||
- OS: [e.g. Debian, Windows, macOS...]
|
|
||||||
- scrcpy version: [e.g. 1.12.1]
|
_Also read the [FAQ] and check if your [issue][issues] already exists._
|
||||||
- installation method: [e.g. manual build, apt, snap, brew, Windows release...]
|
|
||||||
- device model:
|
[FAQ]: https://github.com/Genymobile/scrcpy/blob/master/FAQ.md
|
||||||
- Android version: [e.g. 10]
|
[issues]: https://github.com/Genymobile/scrcpy/issues
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
|
||||||
|
- **OS:** [e.g. Debian, Windows, macOS...]
|
||||||
|
- **Scrcpy version:** [e.g. 2.5]
|
||||||
|
- **Installation method:** [e.g. manual build, apt, snap, brew, Windows release...]
|
||||||
|
- **Device model:**
|
||||||
|
- **Android version:** [e.g. 14]
|
||||||
|
|
||||||
|
## Describe the bug
|
||||||
|
|
||||||
**Describe the bug**
|
|
||||||
A clear and concise description of what the bug is.
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
On errors, please provide the output of the console (and `adb logcat` if relevant).
|
On errors, please provide the output of the console (and `adb logcat` if relevant).
|
||||||
|
8
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
8
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
name: Question
|
||||||
|
about: Ask a question about scrcpy
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
32
README.md
32
README.md
@ -2,7 +2,7 @@
|
|||||||
source for the project. Do not download releases from random websites, even if
|
source for the project. Do not download releases from random websites, even if
|
||||||
their name contains `scrcpy`.**
|
their name contains `scrcpy`.**
|
||||||
|
|
||||||
# scrcpy (v2.4)
|
# scrcpy (v2.7)
|
||||||
|
|
||||||
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
|
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
|
||||||
|
|
||||||
@ -37,6 +37,7 @@ Its features include:
|
|||||||
- [camera mirroring](doc/camera.md) (Android 12+)
|
- [camera mirroring](doc/camera.md) (Android 12+)
|
||||||
- [mirroring as a webcam (V4L2)](doc/v4l2.md) (Linux-only)
|
- [mirroring as a webcam (V4L2)](doc/v4l2.md) (Linux-only)
|
||||||
- physical [keyboard][hid-keyboard] and [mouse][hid-mouse] simulation (HID)
|
- physical [keyboard][hid-keyboard] and [mouse][hid-mouse] simulation (HID)
|
||||||
|
- [gamepad](doc/gamepad.md) support
|
||||||
- [OTG mode](doc/otg.md)
|
- [OTG mode](doc/otg.md)
|
||||||
- and more…
|
- and more…
|
||||||
|
|
||||||
@ -53,10 +54,16 @@ Make sure you [enabled USB debugging][enable-adb] on your device(s).
|
|||||||
|
|
||||||
[enable-adb]: https://developer.android.com/studio/debug/dev-options#enable
|
[enable-adb]: https://developer.android.com/studio/debug/dev-options#enable
|
||||||
|
|
||||||
On some devices, you also need to enable [an additional option][control] `USB
|
On some devices (especially Xiaomi), you might get the following error:
|
||||||
debugging (Security Settings)` (this is an item different from `USB debugging`)
|
|
||||||
to control it using a keyboard and mouse. Rebooting the device is necessary once
|
```
|
||||||
this option is set.
|
java.lang.SecurityException: 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
|
||||||
|
(Security Settings)` (this is an item different from `USB debugging`) to control
|
||||||
|
it using a keyboard and mouse. Rebooting the device is necessary once this
|
||||||
|
option is set.
|
||||||
|
|
||||||
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
|
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
|
||||||
|
|
||||||
@ -105,6 +112,13 @@ Here are just some common examples.
|
|||||||
scrcpy --otg
|
scrcpy --otg
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- Control the device using gamepad controllers plugged into the computer:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --gamepad=uhid
|
||||||
|
scrcpy -G # short version
|
||||||
|
```
|
||||||
|
|
||||||
## User documentation
|
## User documentation
|
||||||
|
|
||||||
The application provides a lot of features and configuration options. They are
|
The application provides a lot of features and configuration options. They are
|
||||||
@ -116,6 +130,7 @@ documented in the following pages:
|
|||||||
- [Control](doc/control.md)
|
- [Control](doc/control.md)
|
||||||
- [Keyboard](doc/keyboard.md)
|
- [Keyboard](doc/keyboard.md)
|
||||||
- [Mouse](doc/mouse.md)
|
- [Mouse](doc/mouse.md)
|
||||||
|
- [Gamepad](doc/gamepad.md)
|
||||||
- [Device](doc/device.md)
|
- [Device](doc/device.md)
|
||||||
- [Window](doc/window.md)
|
- [Window](doc/window.md)
|
||||||
- [Recording](doc/recording.md)
|
- [Recording](doc/recording.md)
|
||||||
@ -148,11 +163,14 @@ documented in the following pages:
|
|||||||
|
|
||||||
## Contact
|
## Contact
|
||||||
|
|
||||||
If you encounter a bug, please read the [FAQ](FAQ.md) first, then open an [issue].
|
You can open an [issue] for bug reports, feature requests or general questions.
|
||||||
|
|
||||||
|
For bug reports, please read the [FAQ](FAQ.md) first, you might find a solution
|
||||||
|
to your problem immediately.
|
||||||
|
|
||||||
[issue]: https://github.com/Genymobile/scrcpy/issues
|
[issue]: https://github.com/Genymobile/scrcpy/issues
|
||||||
|
|
||||||
For general questions or discussions, you can also use:
|
You can also use:
|
||||||
|
|
||||||
- Reddit: [`r/scrcpy`](https://www.reddit.com/r/scrcpy)
|
- Reddit: [`r/scrcpy`](https://www.reddit.com/r/scrcpy)
|
||||||
- Twitter: [`@scrcpy_app`](https://twitter.com/scrcpy_app)
|
- Twitter: [`@scrcpy_app`](https://twitter.com/scrcpy_app)
|
||||||
|
@ -6,6 +6,7 @@ _scrcpy() {
|
|||||||
--audio-buffer=
|
--audio-buffer=
|
||||||
--audio-codec=
|
--audio-codec=
|
||||||
--audio-codec-options=
|
--audio-codec-options=
|
||||||
|
--audio-dup
|
||||||
--audio-encoder=
|
--audio-encoder=
|
||||||
--audio-source=
|
--audio-source=
|
||||||
--audio-output-buffer=
|
--audio-output-buffer=
|
||||||
@ -25,6 +26,8 @@ _scrcpy() {
|
|||||||
-e --select-tcpip
|
-e --select-tcpip
|
||||||
-f --fullscreen
|
-f --fullscreen
|
||||||
--force-adb-forward
|
--force-adb-forward
|
||||||
|
-G
|
||||||
|
--gamepad=
|
||||||
-h --help
|
-h --help
|
||||||
-K
|
-K
|
||||||
--keyboard=
|
--keyboard=
|
||||||
@ -50,6 +53,7 @@ _scrcpy() {
|
|||||||
--no-downsize-on-error
|
--no-downsize-on-error
|
||||||
--no-key-repeat
|
--no-key-repeat
|
||||||
--no-mipmaps
|
--no-mipmaps
|
||||||
|
--no-mouse-hover
|
||||||
--no-power-on
|
--no-power-on
|
||||||
--no-video
|
--no-video
|
||||||
--no-video-playback
|
--no-video-playback
|
||||||
@ -110,7 +114,7 @@ _scrcpy() {
|
|||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
--audio-source)
|
--audio-source)
|
||||||
COMPREPLY=($(compgen -W 'output mic' -- "$cur"))
|
COMPREPLY=($(compgen -W 'output mic playback' -- "$cur"))
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
--camera-facing)
|
--camera-facing)
|
||||||
@ -125,6 +129,10 @@ _scrcpy() {
|
|||||||
COMPREPLY=($(compgen -W 'disabled sdk uhid aoa' -- "$cur"))
|
COMPREPLY=($(compgen -W 'disabled sdk uhid aoa' -- "$cur"))
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
|
--gamepad)
|
||||||
|
COMPREPLY=($(compgen -W 'disabled uhid aoa' -- "$cur"))
|
||||||
|
return
|
||||||
|
;;
|
||||||
--orientation|--display-orientation)
|
--orientation|--display-orientation)
|
||||||
COMPREPLY=($(compgen -W '0 90 180 270 flip0 flip90 flip180 flip270' -- "$cur"))
|
COMPREPLY=($(compgen -W '0 90 180 270 flip0 flip90 flip180 flip270' -- "$cur"))
|
||||||
return
|
return
|
||||||
|
@ -13,8 +13,9 @@ arguments=(
|
|||||||
'--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=[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-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-encoder=[Use a specific MediaCodec audio encoder]'
|
||||||
'--audio-source=[Select the audio source]:source:(output mic)'
|
'--audio-source=[Select the audio source]:source:(output mic playback)'
|
||||||
'--audio-output-buffer=[Configure the size of the SDL audio output buffer (in milliseconds)]'
|
'--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]'
|
{-b,--video-bit-rate=}'[Encode the video at the given bit-rate]'
|
||||||
'--camera-ar=[Select the camera size by its aspect ratio]'
|
'--camera-ar=[Select the camera size by its aspect ratio]'
|
||||||
@ -32,8 +33,10 @@ arguments=(
|
|||||||
{-e,--select-tcpip}'[Use TCP/IP device]'
|
{-e,--select-tcpip}'[Use TCP/IP device]'
|
||||||
{-f,--fullscreen}'[Start in fullscreen]'
|
{-f,--fullscreen}'[Start in fullscreen]'
|
||||||
'--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]'
|
'--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)]'
|
||||||
|
'--gamepad=[Set the gamepad input mode]:mode:(disabled uhid aoa)'
|
||||||
{-h,--help}'[Print the help]'
|
{-h,--help}'[Print the help]'
|
||||||
'-K[Use UHID keyboard (same as --keyboard=uhid)]'
|
'-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)'
|
'--keyboard=[Set the keyboard input mode]:mode:(disabled sdk uhid aoa)'
|
||||||
'--kill-adb-on-close[Kill adb when scrcpy terminates]'
|
'--kill-adb-on-close[Kill adb when scrcpy terminates]'
|
||||||
'--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]'
|
'--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]'
|
||||||
@ -43,7 +46,7 @@ arguments=(
|
|||||||
'--list-encoders[List video and audio encoders available on the device]'
|
'--list-encoders[List video and audio encoders available on the device]'
|
||||||
'--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 90 180 270)'
|
'--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 90 180 270)'
|
||||||
{-m,--max-size=}'[Limit both the width and height of the video to value]'
|
{-m,--max-size=}'[Limit both the width and height of the video to value]'
|
||||||
'-M[Use UHID mouse (same as --mouse=uhid)]'
|
'-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]'
|
'--max-fps=[Limit the frame rate of screen capture]'
|
||||||
'--mouse=[Set the mouse input mode]:mode:(disabled sdk uhid aoa)'
|
'--mouse=[Set the mouse input mode]:mode:(disabled sdk uhid aoa)'
|
||||||
'--mouse-bind=[Configure bindings of secondary clicks]'
|
'--mouse-bind=[Configure bindings of secondary clicks]'
|
||||||
@ -56,6 +59,7 @@ arguments=(
|
|||||||
'--no-downsize-on-error[Disable lowering definition on MediaCodec error]'
|
'--no-downsize-on-error[Disable lowering definition on MediaCodec error]'
|
||||||
'--no-key-repeat[Do not forward repeated key events when a key is held down]'
|
'--no-key-repeat[Do not forward repeated key events when a key is held down]'
|
||||||
'--no-mipmaps[Disable the generation of mipmaps]'
|
'--no-mipmaps[Disable the generation of mipmaps]'
|
||||||
|
'--no-mouse-hover[Do not forward mouse hover events]'
|
||||||
'--no-power-on[Do not power on the device on start]'
|
'--no-power-on[Do not power on the device on start]'
|
||||||
'--no-video[Disable video forwarding]'
|
'--no-video[Disable video forwarding]'
|
||||||
'--no-video-playback[Disable video playback]'
|
'--no-video-playback[Disable video playback]'
|
||||||
|
@ -4,10 +4,10 @@ DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
|
|||||||
cd "$DEPS_DIR"
|
cd "$DEPS_DIR"
|
||||||
. common
|
. common
|
||||||
|
|
||||||
VERSION=34.0.5
|
VERSION=35.0.0
|
||||||
FILENAME=platform-tools_r$VERSION-windows.zip
|
FILENAME=platform-tools_r$VERSION-windows.zip
|
||||||
PROJECT_DIR=platform-tools-$VERSION
|
PROJECT_DIR=platform-tools-$VERSION
|
||||||
SHA256SUM=3f8320152704377de150418a3c4c9d07d16d80a6c0d0d8f7289c22c499e33571
|
SHA256SUM=7ab78a8f8b305ae4d0de647d99c43599744de61a0838d3a47bda0cdffefee87e
|
||||||
|
|
||||||
cd "$SOURCES_DIR"
|
cd "$SOURCES_DIR"
|
||||||
|
|
||||||
|
@ -4,10 +4,10 @@ DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
|
|||||||
cd "$DEPS_DIR"
|
cd "$DEPS_DIR"
|
||||||
. common
|
. common
|
||||||
|
|
||||||
VERSION=6.1.1
|
VERSION=7.0.2
|
||||||
FILENAME=ffmpeg-$VERSION.tar.xz
|
FILENAME=ffmpeg-$VERSION.tar.xz
|
||||||
PROJECT_DIR=ffmpeg-$VERSION
|
PROJECT_DIR=ffmpeg-$VERSION
|
||||||
SHA256SUM=8684f4b00f94b85461884c3719382f1261f0d9eb3d59640a1f4ac0873616f968
|
SHA256SUM=8646515b638a3ad303e23af6a3587734447cb8fc0a0c064ecdb8e95c4fd8b389
|
||||||
|
|
||||||
cd "$SOURCES_DIR"
|
cd "$SOURCES_DIR"
|
||||||
|
|
||||||
@ -17,7 +17,6 @@ then
|
|||||||
else
|
else
|
||||||
get_file "https://ffmpeg.org/releases/$FILENAME" "$FILENAME" "$SHA256SUM"
|
get_file "https://ffmpeg.org/releases/$FILENAME" "$FILENAME" "$SHA256SUM"
|
||||||
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
|
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
|
||||||
patch -d "$PROJECT_DIR" -p1 < "$PATCHES_DIR"/ffmpeg-6.1-fix-build.patch
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p "$BUILD_DIR/$PROJECT_DIR"
|
mkdir -p "$BUILD_DIR/$PROJECT_DIR"
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
From 03c80197afb324da38c9b70254231e3fdcfa68fc Mon Sep 17 00:00:00 2001
|
|
||||||
From: Romain Vimont <rom@rom1v.com>
|
|
||||||
Date: Sun, 12 Nov 2023 17:58:50 +0100
|
|
||||||
Subject: [PATCH] Fix FFmpeg 6.1 build
|
|
||||||
|
|
||||||
Build failed on tag n6.1 With --enable-decoder=av1 but without
|
|
||||||
--enable-muxer=av1.
|
|
||||||
---
|
|
||||||
libavcodec/Makefile | 2 +-
|
|
||||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
|
||||||
|
|
||||||
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
|
|
||||||
index 580a8d6b54..aff19b670c 100644
|
|
||||||
--- a/libavcodec/Makefile
|
|
||||||
+++ b/libavcodec/Makefile
|
|
||||||
@@ -249,7 +249,7 @@ OBJS-$(CONFIG_ATRAC3PAL_DECODER) += atrac3plusdec.o atrac3plus.o \
|
|
||||||
OBJS-$(CONFIG_ATRAC9_DECODER) += atrac9dec.o
|
|
||||||
OBJS-$(CONFIG_AURA_DECODER) += cyuv.o
|
|
||||||
OBJS-$(CONFIG_AURA2_DECODER) += aura.o
|
|
||||||
-OBJS-$(CONFIG_AV1_DECODER) += av1dec.o
|
|
||||||
+OBJS-$(CONFIG_AV1_DECODER) += av1dec.o av1_parse.o
|
|
||||||
OBJS-$(CONFIG_AV1_CUVID_DECODER) += cuviddec.o
|
|
||||||
OBJS-$(CONFIG_AV1_MEDIACODEC_DECODER) += mediacodecdec.o
|
|
||||||
OBJS-$(CONFIG_AV1_MEDIACODEC_ENCODER) += mediacodecenc.o
|
|
||||||
--
|
|
||||||
2.42.0
|
|
||||||
|
|
@ -4,10 +4,10 @@ DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
|
|||||||
cd "$DEPS_DIR"
|
cd "$DEPS_DIR"
|
||||||
. common
|
. common
|
||||||
|
|
||||||
VERSION=2.28.5
|
VERSION=2.30.7
|
||||||
FILENAME=SDL-$VERSION.tar.gz
|
FILENAME=SDL-$VERSION.tar.gz
|
||||||
PROJECT_DIR=SDL-release-$VERSION
|
PROJECT_DIR=SDL-release-$VERSION
|
||||||
SHA256SUM=9f0556e4a24ef5b267010038ad9e9948b62f236d5bcc4b22179f95ef62d84023
|
SHA256SUM=1578c96f62c9ae36b64e431b2aa0e0b0fd07c275dedbc694afc38e19056688f5
|
||||||
|
|
||||||
cd "$SOURCES_DIR"
|
cd "$SOURCES_DIR"
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ src = [
|
|||||||
'src/demuxer.c',
|
'src/demuxer.c',
|
||||||
'src/device_msg.c',
|
'src/device_msg.c',
|
||||||
'src/display.c',
|
'src/display.c',
|
||||||
|
'src/events.c',
|
||||||
'src/icon.c',
|
'src/icon.c',
|
||||||
'src/file_pusher.c',
|
'src/file_pusher.c',
|
||||||
'src/fps_counter.c',
|
'src/fps_counter.c',
|
||||||
@ -31,10 +32,12 @@ src = [
|
|||||||
'src/screen.c',
|
'src/screen.c',
|
||||||
'src/server.c',
|
'src/server.c',
|
||||||
'src/version.c',
|
'src/version.c',
|
||||||
|
'src/hid/hid_gamepad.c',
|
||||||
'src/hid/hid_keyboard.c',
|
'src/hid/hid_keyboard.c',
|
||||||
'src/hid/hid_mouse.c',
|
'src/hid/hid_mouse.c',
|
||||||
'src/trait/frame_source.c',
|
'src/trait/frame_source.c',
|
||||||
'src/trait/packet_source.c',
|
'src/trait/packet_source.c',
|
||||||
|
'src/uhid/gamepad_uhid.c',
|
||||||
'src/uhid/keyboard_uhid.c',
|
'src/uhid/keyboard_uhid.c',
|
||||||
'src/uhid/mouse_uhid.c',
|
'src/uhid/mouse_uhid.c',
|
||||||
'src/uhid/uhid_output.c',
|
'src/uhid/uhid_output.c',
|
||||||
@ -93,6 +96,7 @@ usb_support = get_option('usb')
|
|||||||
if usb_support
|
if usb_support
|
||||||
src += [
|
src += [
|
||||||
'src/usb/aoa_hid.c',
|
'src/usb/aoa_hid.c',
|
||||||
|
'src/usb/gamepad_aoa.c',
|
||||||
'src/usb/keyboard_aoa.c',
|
'src/usb/keyboard_aoa.c',
|
||||||
'src/usb/mouse_aoa.c',
|
'src/usb/mouse_aoa.c',
|
||||||
'src/usb/scrcpy_otg.c',
|
'src/usb/scrcpy_otg.c',
|
||||||
|
@ -13,7 +13,7 @@ BEGIN
|
|||||||
VALUE "LegalCopyright", "Romain Vimont, Genymobile"
|
VALUE "LegalCopyright", "Romain Vimont, Genymobile"
|
||||||
VALUE "OriginalFilename", "scrcpy.exe"
|
VALUE "OriginalFilename", "scrcpy.exe"
|
||||||
VALUE "ProductName", "scrcpy"
|
VALUE "ProductName", "scrcpy"
|
||||||
VALUE "ProductVersion", "2.4"
|
VALUE "ProductVersion", "2.7"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
BLOCK "VarFileInfo"
|
BLOCK "VarFileInfo"
|
||||||
|
57
app/scrcpy.1
57
app/scrcpy.1
@ -29,7 +29,7 @@ Default is 128K (128000).
|
|||||||
.BI "\-\-audio\-buffer " ms
|
.BI "\-\-audio\-buffer " ms
|
||||||
Configure the audio buffering delay (in milliseconds).
|
Configure the audio buffering delay (in milliseconds).
|
||||||
|
|
||||||
Lower values decrease the latency, but increase the likelyhood of buffer underrun (causing audio glitches).
|
Lower values decrease the latency, but increase the likelihood of buffer underrun (causing audio glitches).
|
||||||
|
|
||||||
Default is 50.
|
Default is 50.
|
||||||
|
|
||||||
@ -49,6 +49,12 @@ The list of possible codec options is available in the Android documentation:
|
|||||||
|
|
||||||
<https://d.android.com/reference/android/media/MediaFormat>
|
<https://d.android.com/reference/android/media/MediaFormat>
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-\-audio\-dup
|
||||||
|
Duplicate audio (capture and keep playing on the device).
|
||||||
|
|
||||||
|
This feature is only available with --audio-source=playback.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-audio\-encoder " name
|
.BI "\-\-audio\-encoder " name
|
||||||
Use a specific MediaCodec audio encoder (depending on the codec provided by \fB\-\-audio\-codec\fR).
|
Use a specific MediaCodec audio encoder (depending on the codec provided by \fB\-\-audio\-codec\fR).
|
||||||
@ -57,7 +63,13 @@ The available encoders can be listed by \fB\-\-list\-encoders\fR.
|
|||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-audio\-source " source
|
.BI "\-\-audio\-source " source
|
||||||
Select the audio source (output or mic).
|
Select the audio source (output, mic or playback).
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
Default is output.
|
Default is output.
|
||||||
|
|
||||||
@ -163,13 +175,28 @@ Start in fullscreen.
|
|||||||
.B \-\-force\-adb\-forward
|
.B \-\-force\-adb\-forward
|
||||||
Do not attempt to use "adb reverse" to connect to the device.
|
Do not attempt to use "adb reverse" to connect to the device.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-G
|
||||||
|
Same as \fB\-\-gamepad=uhid\fR, or \fB\-\-keyboard=aoa\fR if \fB\-\-otg\fR is set.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "\-\-gamepad " mode
|
||||||
|
Select how to send gamepad inputs to the device.
|
||||||
|
|
||||||
|
Possible values are "disabled", "uhid" and "aoa":
|
||||||
|
|
||||||
|
- "disabled" does not send gamepad inputs to the device.
|
||||||
|
- "uhid" simulates physical HID gamepads using the Linux HID kernel module on the device.
|
||||||
|
- "aoa" simulates physical HID gamepads using the AOAv2 protocol. It may only work over USB.
|
||||||
|
|
||||||
|
Also see \fB\-\-keyboard\f and R\fB\-\-mouse\fR.
|
||||||
.TP
|
.TP
|
||||||
.B \-h, \-\-help
|
.B \-h, \-\-help
|
||||||
Print this help.
|
Print this help.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-K
|
.B \-K
|
||||||
Same as \fB\-\-keyboard=uhid\fR.
|
Same as \fB\-\-keyboard=uhid\fR, or \fB\-\-keyboard=aoa\fR if \fB\-\-otg\fR is set.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-keyboard " mode
|
.BI "\-\-keyboard " mode
|
||||||
@ -188,7 +215,7 @@ For "uhid" and "aoa", the keyboard layout must be configured (once and for all)
|
|||||||
|
|
||||||
This option is only available when the HID keyboard is enabled (or a physical keyboard is connected).
|
This option is only available when the HID keyboard is enabled (or a physical keyboard is connected).
|
||||||
|
|
||||||
Also see \fB\-\-mouse\fR.
|
Also see \fB\-\-mouse\fR and \fB\-\-gamepad\fR.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-\-kill\-adb\-on\-close
|
.B \-\-kill\-adb\-on\-close
|
||||||
@ -234,7 +261,7 @@ Default is 0 (unlimited).
|
|||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-M
|
.B \-M
|
||||||
Same as \fB\-\-mouse=uhid\fR.
|
Same as \fB\-\-mouse=uhid\fR, or \fB\-\-mouse=aoa\fR if \fB\-\-otg\fR is set.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-max\-fps " value
|
.BI "\-\-max\-fps " value
|
||||||
@ -255,13 +282,17 @@ In "uhid" and "aoa" modes, the computer mouse is captured to control the device
|
|||||||
|
|
||||||
LAlt, LSuper or RSuper toggle the capture mode, to give control of the mouse back to the computer.
|
LAlt, LSuper or RSuper toggle the capture mode, to give control of the mouse back to the computer.
|
||||||
|
|
||||||
Also see \fB\-\-keyboard\fR.
|
Also see \fB\-\-keyboard\fR and \fB\-\-gamepad\fR.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-mouse\-bind " xxxx
|
.BI "\-\-mouse\-bind " xxxx[:xxxx]
|
||||||
Configure bindings of secondary clicks.
|
Configure bindings of secondary clicks.
|
||||||
|
|
||||||
The argument must be exactly 4 characters, one for each secondary click (in order: right click, middle click, 4th click, 5th click).
|
The argument must be one or two sequences (separated by ':') of exactly 4 characters, one for each secondary click (in order: right click, middle click, 4th click, 5th click).
|
||||||
|
|
||||||
|
The first sequence defines the primary bindings, used when a mouse button is pressed alone. The second sequence defines the secondary bindings, used when a mouse button is pressed while the Shift key is held.
|
||||||
|
|
||||||
|
If the second sequence of bindings is omitted, then it is the same as the first one.
|
||||||
|
|
||||||
Each character must be one of the following:
|
Each character must be one of the following:
|
||||||
|
|
||||||
@ -272,7 +303,7 @@ Each character must be one of the following:
|
|||||||
- 's': trigger shortcut APP_SWITCH
|
- 's': trigger shortcut APP_SWITCH
|
||||||
- 'n': trigger shortcut "expand notification panel"
|
- 'n': trigger shortcut "expand notification panel"
|
||||||
|
|
||||||
Default is 'bhsn' for SDK mouse, and '++++' for AOA and UHID.
|
Default is 'bhsn:++++' for SDK mouse, and '++++:bhsn' for AOA and UHID.
|
||||||
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
@ -317,6 +348,10 @@ Do not forward repeated key events when a key is held down.
|
|||||||
.B \-\-no\-mipmaps
|
.B \-\-no\-mipmaps
|
||||||
If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then mipmaps are automatically generated to improve downscaling quality. This option disables the generation of mipmaps.
|
If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then mipmaps are automatically generated to improve downscaling quality. This option disables the generation of mipmaps.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-\-no\-mouse\-hover
|
||||||
|
Do not forward mouse hover (mouse motion without any clicks) events.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-\-no\-power\-on
|
.B \-\-no\-power\-on
|
||||||
Do not power on the device on start.
|
Do not power on the device on start.
|
||||||
@ -349,7 +384,7 @@ If any of \fB\-\-hid\-keyboard\fR or \fB\-\-hid\-mouse\fR is set, only enable ke
|
|||||||
|
|
||||||
It may only work over USB.
|
It may only work over USB.
|
||||||
|
|
||||||
See \fB\-\-hid\-keyboard\fR and \fB\-\-hid\-mouse\fR.
|
See \fB\-\-keyboard\fR, \fB\-\-mouse\fR and \fB\-\-gamepad\fR.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-p, \-\-port " port\fR[:\fIport\fR]
|
.BI "\-p, \-\-port " port\fR[:\fIport\fR]
|
||||||
@ -359,7 +394,7 @@ Default is 27183:27199.
|
|||||||
|
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-pause\-on\-exit\fR[=\fImode\fR]
|
\fB\-\-pause\-on\-exit\fR[=\fImode\fR]
|
||||||
Configure pause on exit. Possible values are "true" (always pause on exit), "false" (never pause on exit) and "if-error" (pause only if an error occured).
|
Configure pause on exit. Possible values are "true" (always pause on exit), "false" (never pause on exit) and "if-error" (pause only if an error occurred).
|
||||||
|
|
||||||
This is useful to prevent the terminal window from automatically closing, so that error messages can be read.
|
This is useful to prevent the terminal window from automatically closing, so that error messages can be read.
|
||||||
|
|
||||||
|
@ -633,7 +633,7 @@ enum android_keycode {
|
|||||||
* Toggles between BS and CS digital satellite services. */
|
* Toggles between BS and CS digital satellite services. */
|
||||||
AKEYCODE_TV_SATELLITE_SERVICE = 240,
|
AKEYCODE_TV_SATELLITE_SERVICE = 240,
|
||||||
/** Toggle Network key.
|
/** Toggle Network key.
|
||||||
* Toggles selecting broacast services. */
|
* Toggles selecting broadcast services. */
|
||||||
AKEYCODE_TV_NETWORK = 241,
|
AKEYCODE_TV_NETWORK = 241,
|
||||||
/** Antenna/Cable key.
|
/** Antenna/Cable key.
|
||||||
* Toggles broadcast input source between antenna and cable. */
|
* Toggles broadcast input source between antenna and cable. */
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
#define SC_AUDIO_PLAYER_NDEBUG // comment to debug
|
//#define SC_AUDIO_PLAYER_DEBUG // uncomment to debug
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Real-time audio player with configurable latency
|
* Real-time audio player with configurable latency
|
||||||
@ -72,7 +72,7 @@ sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) {
|
|||||||
size_t len = len_int;
|
size_t len = len_int;
|
||||||
uint32_t count = TO_SAMPLES(len);
|
uint32_t count = TO_SAMPLES(len);
|
||||||
|
|
||||||
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
#ifdef SC_AUDIO_PLAYER_DEBUG
|
||||||
LOGD("[Audio] SDL callback requests %" PRIu32 " samples", count);
|
LOGD("[Audio] SDL callback requests %" PRIu32 " samples", count);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -162,7 +162,7 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
|
|||||||
// swr_convert() returns the number of samples which would have been
|
// swr_convert() returns the number of samples which would have been
|
||||||
// written if the buffer was big enough.
|
// written if the buffer was big enough.
|
||||||
uint32_t samples = MIN(ret, dst_nb_samples);
|
uint32_t samples = MIN(ret, dst_nb_samples);
|
||||||
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
#ifdef SC_AUDIO_PLAYER_DEBUG
|
||||||
LOGD("[Audio] %" PRIu32 " samples written to buffer", samples);
|
LOGD("[Audio] %" PRIu32 " samples written to buffer", samples);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -244,7 +244,7 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
|
|||||||
if (played) {
|
if (played) {
|
||||||
LOGD("[Audio] Buffering threshold exceeded, skipping %" PRIu32
|
LOGD("[Audio] Buffering threshold exceeded, skipping %" PRIu32
|
||||||
" samples", skip_samples);
|
" samples", skip_samples);
|
||||||
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
#ifdef SC_AUDIO_PLAYER_DEBUG
|
||||||
} else {
|
} else {
|
||||||
LOGD("[Audio] Playback not started, skipping %" PRIu32
|
LOGD("[Audio] Playback not started, skipping %" PRIu32
|
||||||
" samples", skip_samples);
|
" samples", skip_samples);
|
||||||
@ -282,7 +282,7 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
|
|||||||
// However, the buffering level must be smoothed
|
// However, the buffering level must be smoothed
|
||||||
sc_average_push(&ap->avg_buffering, can_read);
|
sc_average_push(&ap->avg_buffering, can_read);
|
||||||
|
|
||||||
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
#ifdef SC_AUDIO_PLAYER_DEBUG
|
||||||
LOGD("[Audio] can_read=%" PRIu32 " avg_buffering=%f",
|
LOGD("[Audio] can_read=%" PRIu32 " avg_buffering=%f",
|
||||||
can_read, sc_average_get(&ap->avg_buffering));
|
can_read, sc_average_get(&ap->avg_buffering));
|
||||||
#endif
|
#endif
|
||||||
|
314
app/src/cli.c
314
app/src/cli.c
@ -99,6 +99,9 @@ enum {
|
|||||||
OPT_HID_MOUSE_DEPRECATED,
|
OPT_HID_MOUSE_DEPRECATED,
|
||||||
OPT_NO_WINDOW,
|
OPT_NO_WINDOW,
|
||||||
OPT_MOUSE_BIND,
|
OPT_MOUSE_BIND,
|
||||||
|
OPT_NO_MOUSE_HOVER,
|
||||||
|
OPT_AUDIO_DUP,
|
||||||
|
OPT_GAMEPAD,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_option {
|
struct sc_option {
|
||||||
@ -154,7 +157,7 @@ static const struct sc_option options[] = {
|
|||||||
.argdesc = "ms",
|
.argdesc = "ms",
|
||||||
.text = "Configure the audio buffering delay (in milliseconds).\n"
|
.text = "Configure the audio buffering delay (in milliseconds).\n"
|
||||||
"Lower values decrease the latency, but increase the "
|
"Lower values decrease the latency, but increase the "
|
||||||
"likelyhood of buffer underrun (causing audio glitches).\n"
|
"likelihood of buffer underrun (causing audio glitches).\n"
|
||||||
"Default is 50.",
|
"Default is 50.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -176,6 +179,13 @@ static const struct sc_option options[] = {
|
|||||||
"Android documentation: "
|
"Android documentation: "
|
||||||
"<https://d.android.com/reference/android/media/MediaFormat>",
|
"<https://d.android.com/reference/android/media/MediaFormat>",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.longopt_id = OPT_AUDIO_DUP,
|
||||||
|
.longopt = "audio-dup",
|
||||||
|
.text = "Duplicate audio (capture and keep playing on the device).\n"
|
||||||
|
"This feature is only available with --audio-source=playback."
|
||||||
|
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_AUDIO_ENCODER,
|
.longopt_id = OPT_AUDIO_ENCODER,
|
||||||
.longopt = "audio-encoder",
|
.longopt = "audio-encoder",
|
||||||
@ -188,7 +198,13 @@ static const struct sc_option options[] = {
|
|||||||
.longopt_id = OPT_AUDIO_SOURCE,
|
.longopt_id = OPT_AUDIO_SOURCE,
|
||||||
.longopt = "audio-source",
|
.longopt = "audio-source",
|
||||||
.argdesc = "source",
|
.argdesc = "source",
|
||||||
.text = "Select the audio source (output or mic).\n"
|
.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 "
|
||||||
|
"captured).\n"
|
||||||
|
"The \"mic\" source captures the microphone.\n"
|
||||||
"Default is output.",
|
"Default is output.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -357,6 +373,23 @@ static const struct sc_option options[] = {
|
|||||||
.longopt_id = OPT_FORWARD_ALL_CLICKS,
|
.longopt_id = OPT_FORWARD_ALL_CLICKS,
|
||||||
.longopt = "forward-all-clicks",
|
.longopt = "forward-all-clicks",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.shortopt = 'G',
|
||||||
|
.text = "Same as --gamepad=uhid, or --gamepad=aoa if --otg is set.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.longopt_id = OPT_GAMEPAD,
|
||||||
|
.longopt = "gamepad",
|
||||||
|
.argdesc = "mode",
|
||||||
|
.text = "Select how to send gamepad inputs to the device.\n"
|
||||||
|
"Possible values are \"disabled\", \"uhid\" and \"aoa\".\n"
|
||||||
|
"\"disabled\" does not send gamepad inputs to the device.\n"
|
||||||
|
"\"uhid\" simulates physical HID gamepads using the Linux UHID "
|
||||||
|
"kernel module on the device.\n"
|
||||||
|
"\"aoa\" simulates physical gamepads using the AOAv2 protocol."
|
||||||
|
"It may only work over USB.\n"
|
||||||
|
"Also see --keyboard and --mouse.",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.shortopt = 'h',
|
.shortopt = 'h',
|
||||||
.longopt = "help",
|
.longopt = "help",
|
||||||
@ -364,7 +397,7 @@ static const struct sc_option options[] = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
.shortopt = 'K',
|
.shortopt = 'K',
|
||||||
.text = "Same as --keyboard=uhid.",
|
.text = "Same as --keyboard=uhid, or --keyboard=aoa if --otg is set.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_KEYBOARD,
|
.longopt_id = OPT_KEYBOARD,
|
||||||
@ -388,7 +421,7 @@ static const struct sc_option options[] = {
|
|||||||
"start -a android.settings.HARD_KEYBOARD_SETTINGS`.\n"
|
"start -a android.settings.HARD_KEYBOARD_SETTINGS`.\n"
|
||||||
"This option is only available when a HID keyboard is enabled "
|
"This option is only available when a HID keyboard is enabled "
|
||||||
"(or a physical keyboard is connected).\n"
|
"(or a physical keyboard is connected).\n"
|
||||||
"Also see --mouse.",
|
"Also see --mouse and --gamepad.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_KILL_ADB_ON_CLOSE,
|
.longopt_id = OPT_KILL_ADB_ON_CLOSE,
|
||||||
@ -460,7 +493,7 @@ static const struct sc_option options[] = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
.shortopt = 'M',
|
.shortopt = 'M',
|
||||||
.text = "Same as --mouse=uhid.",
|
.text = "Same as --mouse=uhid, or --mouse=aoa if --otg is set.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_MAX_FPS,
|
.longopt_id = OPT_MAX_FPS,
|
||||||
@ -487,16 +520,22 @@ static const struct sc_option options[] = {
|
|||||||
"to control the device directly (relative mouse mode).\n"
|
"to control the device directly (relative mouse mode).\n"
|
||||||
"LAlt, LSuper or RSuper toggle the capture mode, to give "
|
"LAlt, LSuper or RSuper toggle the capture mode, to give "
|
||||||
"control of the mouse back to the computer.\n"
|
"control of the mouse back to the computer.\n"
|
||||||
"Also see --keyboard.",
|
"Also see --keyboard and --gamepad.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_MOUSE_BIND,
|
.longopt_id = OPT_MOUSE_BIND,
|
||||||
.longopt = "mouse-bind",
|
.longopt = "mouse-bind",
|
||||||
.argdesc = "xxxx",
|
.argdesc = "xxxx[:xxxx]",
|
||||||
.text = "Configure bindings of secondary clicks.\n"
|
.text = "Configure bindings of secondary clicks.\n"
|
||||||
"The argument must be exactly 4 characters, one for each "
|
"The argument must be one or two sequences (separated by ':') "
|
||||||
"secondary click (in order: right click, middle click, 4th "
|
"of exactly 4 characters, one for each secondary click (in "
|
||||||
"click, 5th click).\n"
|
"order: right click, middle click, 4th click, 5th click).\n"
|
||||||
|
"The first sequence defines the primary bindings, used when a "
|
||||||
|
"mouse button is pressed alone. The second sequence defines "
|
||||||
|
"the secondary bindings, used when a mouse button is pressed "
|
||||||
|
"while the Shift key is held.\n"
|
||||||
|
"If the second sequence of bindings is omitted, then it is the "
|
||||||
|
"same as the first one.\n"
|
||||||
"Each character must be one of the following:\n"
|
"Each character must be one of the following:\n"
|
||||||
" '+': forward the click to the device\n"
|
" '+': forward the click to the device\n"
|
||||||
" '-': ignore the click\n"
|
" '-': ignore the click\n"
|
||||||
@ -504,7 +543,8 @@ static const struct sc_option options[] = {
|
|||||||
" 'h': trigger shortcut HOME\n"
|
" 'h': trigger shortcut HOME\n"
|
||||||
" 's': trigger shortcut APP_SWITCH\n"
|
" 's': trigger shortcut APP_SWITCH\n"
|
||||||
" 'n': trigger shortcut \"expand notification panel\"\n"
|
" 'n': trigger shortcut \"expand notification panel\"\n"
|
||||||
"Default is 'bhsn' for SDK mouse, and '++++' for AOA and UHID.",
|
"Default is 'bhsn:++++' for SDK mouse, and '++++:bhsn' for AOA "
|
||||||
|
"and UHID.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.shortopt = 'n',
|
.shortopt = 'n',
|
||||||
@ -568,6 +608,12 @@ static const struct sc_option options[] = {
|
|||||||
"mipmaps are automatically generated to improve downscaling "
|
"mipmaps are automatically generated to improve downscaling "
|
||||||
"quality. This option disables the generation of mipmaps.",
|
"quality. This option disables the generation of mipmaps.",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.longopt_id = OPT_NO_MOUSE_HOVER,
|
||||||
|
.longopt = "no-mouse-hover",
|
||||||
|
.text = "Do not forward mouse hover (mouse motion without any clicks) "
|
||||||
|
"events.",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_NO_POWER_ON,
|
.longopt_id = OPT_NO_POWER_ON,
|
||||||
.longopt = "no-power-on",
|
.longopt = "no-power-on",
|
||||||
@ -609,7 +655,7 @@ static const struct sc_option options[] = {
|
|||||||
"Keyboard and mouse may be disabled separately using"
|
"Keyboard and mouse may be disabled separately using"
|
||||||
"--keyboard=disabled and --mouse=disabled.\n"
|
"--keyboard=disabled and --mouse=disabled.\n"
|
||||||
"It may only work over USB.\n"
|
"It may only work over USB.\n"
|
||||||
"See --keyboard and --mouse.",
|
"See --keyboard, --mouse and --gamepad.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.shortopt = 'p',
|
.shortopt = 'p',
|
||||||
@ -626,7 +672,7 @@ static const struct sc_option options[] = {
|
|||||||
.optional_arg = true,
|
.optional_arg = true,
|
||||||
.text = "Configure pause on exit. Possible values are \"true\" (always "
|
.text = "Configure pause on exit. Possible values are \"true\" (always "
|
||||||
"pause on exit), \"false\" (never pause on exit) and "
|
"pause on exit), \"false\" (never pause on exit) and "
|
||||||
"\"if-error\" (pause only if an error occured).\n"
|
"\"if-error\" (pause only if an error occurred).\n"
|
||||||
"This is useful to prevent the terminal window from "
|
"This is useful to prevent the terminal window from "
|
||||||
"automatically closing, so that error messages can be read.\n"
|
"automatically closing, so that error messages can be read.\n"
|
||||||
"Default is \"false\".\n"
|
"Default is \"false\".\n"
|
||||||
@ -1321,7 +1367,7 @@ print_exit_status(const struct sc_exit_status *status, unsigned cols) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(strlen(text) >= 9); // Contains at least the initial identation
|
assert(strlen(text) >= 9); // Contains at least the initial indentation
|
||||||
|
|
||||||
// text + 9 to remove the initial indentation
|
// text + 9 to remove the initial indentation
|
||||||
printf(" %3d %s\n", status->value, text + 9);
|
printf(" %3d %s\n", status->value, text + 9);
|
||||||
@ -1445,18 +1491,6 @@ parse_max_size(const char *s, uint16_t *max_size) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
|
||||||
parse_max_fps(const char *s, uint16_t *max_fps) {
|
|
||||||
long value;
|
|
||||||
bool ok = parse_integer_arg(s, &value, false, 0, 0xFFFF, "max fps");
|
|
||||||
if (!ok) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
*max_fps = (uint16_t) value;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
parse_buffering_time(const char *s, sc_tick *tick) {
|
parse_buffering_time(const char *s, sc_tick *tick) {
|
||||||
long value;
|
long value;
|
||||||
@ -1917,7 +1951,13 @@ parse_audio_source(const char *optarg, enum sc_audio_source *source) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGE("Unsupported audio source: %s (expected output or mic)", optarg);
|
if (!strcmp(optarg, "playback")) {
|
||||||
|
*source = SC_AUDIO_SOURCE_PLAYBACK;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGE("Unsupported audio source: %s (expected output, mic or playback)",
|
||||||
|
optarg);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2024,6 +2064,32 @@ parse_mouse(const char *optarg, enum sc_mouse_input_mode *mode) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
parse_gamepad(const char *optarg, enum sc_gamepad_input_mode *mode) {
|
||||||
|
if (!strcmp(optarg, "disabled")) {
|
||||||
|
*mode = SC_GAMEPAD_INPUT_MODE_DISABLED;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(optarg, "uhid")) {
|
||||||
|
*mode = SC_GAMEPAD_INPUT_MODE_UHID;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(optarg, "aoa")) {
|
||||||
|
#ifdef HAVE_USB
|
||||||
|
*mode = SC_GAMEPAD_INPUT_MODE_AOA;
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
LOGE("--gamepad=aoa is disabled.");
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGE("Unsupported gamepad: %s (expected disabled or aoa)", optarg);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
parse_time_limit(const char *s, sc_tick *tick) {
|
parse_time_limit(const char *s, sc_tick *tick) {
|
||||||
long value;
|
long value;
|
||||||
@ -2088,24 +2154,46 @@ parse_mouse_binding(char c, enum sc_mouse_binding *b) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
parse_mouse_bindings(const char *s, struct sc_mouse_bindings *mb) {
|
parse_mouse_binding_set(const char *s, struct sc_mouse_binding_set *mbs) {
|
||||||
if (strlen(s) != 4) {
|
assert(strlen(s) >= 4);
|
||||||
LOGE("Invalid mouse bindings: '%s' (expected exactly 4 characters from "
|
|
||||||
"{'+', '-', 'b', 'h', 's', 'n'})", s);
|
if (!parse_mouse_binding(s[0], &mbs->right_click)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!parse_mouse_binding(s[1], &mbs->middle_click)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!parse_mouse_binding(s[2], &mbs->click4)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!parse_mouse_binding(s[3], &mbs->click5)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parse_mouse_binding(s[0], &mb->right_click)) {
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
parse_mouse_bindings(const char *s, struct sc_mouse_bindings *mb) {
|
||||||
|
size_t len = strlen(s);
|
||||||
|
// either "xxxx" or "xxxx:xxxx"
|
||||||
|
if (len != 4 && (len != 9 || s[4] != ':')) {
|
||||||
|
LOGE("Invalid mouse bindings: '%s' (expected 'xxxx' or 'xxxx:xxxx', "
|
||||||
|
"with each 'x' being in {'+', '-', 'b', 'h', 's', 'n'})", s);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!parse_mouse_binding(s[1], &mb->middle_click)) {
|
|
||||||
|
if (!parse_mouse_binding_set(s, &mb->pri)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!parse_mouse_binding(s[2], &mb->click4)) {
|
|
||||||
return false;
|
if (len == 9) {
|
||||||
}
|
if (!parse_mouse_binding_set(s + 5, &mb->sec)) {
|
||||||
if (!parse_mouse_binding(s[3], &mb->click5)) {
|
return false;
|
||||||
return false;
|
}
|
||||||
|
} else {
|
||||||
|
// use the same bindings for Shift+click
|
||||||
|
mb->sec = mb->pri;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -2164,7 +2252,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
args->help = true;
|
args->help = true;
|
||||||
break;
|
break;
|
||||||
case 'K':
|
case 'K':
|
||||||
opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_UHID;
|
opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_UHID_OR_AOA;
|
||||||
break;
|
break;
|
||||||
case OPT_KEYBOARD:
|
case OPT_KEYBOARD:
|
||||||
if (!parse_keyboard(optarg, &opts->keyboard_input_mode)) {
|
if (!parse_keyboard(optarg, &opts->keyboard_input_mode)) {
|
||||||
@ -2176,9 +2264,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
"--keyboard=uhid instead.");
|
"--keyboard=uhid instead.");
|
||||||
return false;
|
return false;
|
||||||
case OPT_MAX_FPS:
|
case OPT_MAX_FPS:
|
||||||
if (!parse_max_fps(optarg, &opts->max_fps)) {
|
opts->max_fps = optarg;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 'm':
|
case 'm':
|
||||||
if (!parse_max_size(optarg, &opts->max_size)) {
|
if (!parse_max_size(optarg, &opts->max_size)) {
|
||||||
@ -2186,7 +2272,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'M':
|
case 'M':
|
||||||
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_UHID;
|
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_UHID_OR_AOA;
|
||||||
break;
|
break;
|
||||||
case OPT_MOUSE:
|
case OPT_MOUSE:
|
||||||
if (!parse_mouse(optarg, &opts->mouse_input_mode)) {
|
if (!parse_mouse(optarg, &opts->mouse_input_mode)) {
|
||||||
@ -2198,6 +2284,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case OPT_NO_MOUSE_HOVER:
|
||||||
|
opts->mouse_hover = false;
|
||||||
|
break;
|
||||||
case OPT_HID_MOUSE_DEPRECATED:
|
case OPT_HID_MOUSE_DEPRECATED:
|
||||||
LOGE("--hid-mouse has been removed, use --mouse=aoa or "
|
LOGE("--hid-mouse has been removed, use --mouse=aoa or "
|
||||||
"--mouse=uhid instead.");
|
"--mouse=uhid instead.");
|
||||||
@ -2398,10 +2487,18 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
LOGW("--forward-all-clicks is deprecated, "
|
LOGW("--forward-all-clicks is deprecated, "
|
||||||
"use --mouse-bind=++++ instead.");
|
"use --mouse-bind=++++ instead.");
|
||||||
opts->mouse_bindings = (struct sc_mouse_bindings) {
|
opts->mouse_bindings = (struct sc_mouse_bindings) {
|
||||||
.right_click = SC_MOUSE_BINDING_CLICK,
|
.pri = {
|
||||||
.middle_click = SC_MOUSE_BINDING_CLICK,
|
.right_click = SC_MOUSE_BINDING_CLICK,
|
||||||
.click4 = SC_MOUSE_BINDING_CLICK,
|
.middle_click = SC_MOUSE_BINDING_CLICK,
|
||||||
.click5 = SC_MOUSE_BINDING_CLICK,
|
.click4 = SC_MOUSE_BINDING_CLICK,
|
||||||
|
.click5 = SC_MOUSE_BINDING_CLICK,
|
||||||
|
},
|
||||||
|
.sec = {
|
||||||
|
.right_click = SC_MOUSE_BINDING_CLICK,
|
||||||
|
.middle_click = SC_MOUSE_BINDING_CLICK,
|
||||||
|
.click4 = SC_MOUSE_BINDING_CLICK,
|
||||||
|
.click5 = SC_MOUSE_BINDING_CLICK,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case OPT_LEGACY_PASTE:
|
case OPT_LEGACY_PASTE:
|
||||||
@ -2556,6 +2653,17 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
case OPT_NO_WINDOW:
|
case OPT_NO_WINDOW:
|
||||||
opts->window = false;
|
opts->window = false;
|
||||||
break;
|
break;
|
||||||
|
case OPT_AUDIO_DUP:
|
||||||
|
opts->audio_dup = true;
|
||||||
|
break;
|
||||||
|
case 'G':
|
||||||
|
opts->gamepad_input_mode = SC_GAMEPAD_INPUT_MODE_UHID_OR_AOA;
|
||||||
|
break;
|
||||||
|
case OPT_GAMEPAD:
|
||||||
|
if (!parse_gamepad(optarg, &opts->gamepad_input_mode)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// getopt prints the error message on stderr
|
// getopt prints the error message on stderr
|
||||||
return false;
|
return false;
|
||||||
@ -2673,7 +2781,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
if (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AUTO) {
|
if (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AUTO) {
|
||||||
opts->keyboard_input_mode = otg ? SC_KEYBOARD_INPUT_MODE_AOA
|
opts->keyboard_input_mode = otg ? SC_KEYBOARD_INPUT_MODE_AOA
|
||||||
: SC_KEYBOARD_INPUT_MODE_SDK;
|
: SC_KEYBOARD_INPUT_MODE_SDK;
|
||||||
|
} else if (opts->keyboard_input_mode
|
||||||
|
== SC_KEYBOARD_INPUT_MODE_UHID_OR_AOA) {
|
||||||
|
opts->keyboard_input_mode = otg ? SC_KEYBOARD_INPUT_MODE_AOA
|
||||||
|
: SC_KEYBOARD_INPUT_MODE_UHID;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_AUTO) {
|
if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_AUTO) {
|
||||||
if (otg) {
|
if (otg) {
|
||||||
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_AOA;
|
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_AOA;
|
||||||
@ -2683,34 +2796,51 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
} else {
|
} else {
|
||||||
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_SDK;
|
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_SDK;
|
||||||
}
|
}
|
||||||
|
} else if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_UHID_OR_AOA) {
|
||||||
|
opts->mouse_input_mode = otg ? SC_MOUSE_INPUT_MODE_AOA
|
||||||
|
: SC_MOUSE_INPUT_MODE_UHID;
|
||||||
} else if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK
|
} else if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK
|
||||||
&& !opts->video_playback) {
|
&& !opts->video_playback) {
|
||||||
LOGE("SDK mouse mode requires video playback. Try --mouse=uhid.");
|
LOGE("SDK mouse mode requires video playback. Try --mouse=uhid.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (opts->gamepad_input_mode == SC_GAMEPAD_INPUT_MODE_UHID_OR_AOA) {
|
||||||
|
opts->gamepad_input_mode = otg ? SC_GAMEPAD_INPUT_MODE_AOA
|
||||||
|
: SC_GAMEPAD_INPUT_MODE_UHID;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If mouse bindings are not explictly set, configure default bindings
|
// If mouse bindings are not explicitly set, configure default bindings
|
||||||
if (opts->mouse_bindings.right_click == SC_MOUSE_BINDING_AUTO) {
|
if (opts->mouse_bindings.pri.right_click == SC_MOUSE_BINDING_AUTO) {
|
||||||
assert(opts->mouse_bindings.middle_click == SC_MOUSE_BINDING_AUTO);
|
assert(opts->mouse_bindings.pri.middle_click == SC_MOUSE_BINDING_AUTO);
|
||||||
assert(opts->mouse_bindings.click4 == SC_MOUSE_BINDING_AUTO);
|
assert(opts->mouse_bindings.pri.click4 == SC_MOUSE_BINDING_AUTO);
|
||||||
assert(opts->mouse_bindings.click5 == SC_MOUSE_BINDING_AUTO);
|
assert(opts->mouse_bindings.pri.click5 == SC_MOUSE_BINDING_AUTO);
|
||||||
|
assert(opts->mouse_bindings.sec.right_click == SC_MOUSE_BINDING_AUTO);
|
||||||
|
assert(opts->mouse_bindings.sec.middle_click == SC_MOUSE_BINDING_AUTO);
|
||||||
|
assert(opts->mouse_bindings.sec.click4 == SC_MOUSE_BINDING_AUTO);
|
||||||
|
assert(opts->mouse_bindings.sec.click5 == SC_MOUSE_BINDING_AUTO);
|
||||||
|
|
||||||
|
static struct sc_mouse_binding_set default_shortcuts = {
|
||||||
|
.right_click = SC_MOUSE_BINDING_BACK,
|
||||||
|
.middle_click = SC_MOUSE_BINDING_HOME,
|
||||||
|
.click4 = SC_MOUSE_BINDING_APP_SWITCH,
|
||||||
|
.click5 = SC_MOUSE_BINDING_EXPAND_NOTIFICATION_PANEL,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct sc_mouse_binding_set forward = {
|
||||||
|
.right_click = SC_MOUSE_BINDING_CLICK,
|
||||||
|
.middle_click = SC_MOUSE_BINDING_CLICK,
|
||||||
|
.click4 = SC_MOUSE_BINDING_CLICK,
|
||||||
|
.click5 = SC_MOUSE_BINDING_CLICK,
|
||||||
|
};
|
||||||
|
|
||||||
// By default, forward all clicks only for UHID and AOA
|
// By default, forward all clicks only for UHID and AOA
|
||||||
if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK) {
|
if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK) {
|
||||||
opts->mouse_bindings = (struct sc_mouse_bindings) {
|
opts->mouse_bindings.pri = default_shortcuts;
|
||||||
.right_click = SC_MOUSE_BINDING_BACK,
|
opts->mouse_bindings.sec = forward;
|
||||||
.middle_click = SC_MOUSE_BINDING_HOME,
|
|
||||||
.click4 = SC_MOUSE_BINDING_APP_SWITCH,
|
|
||||||
.click5 = SC_MOUSE_BINDING_EXPAND_NOTIFICATION_PANEL,
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
opts->mouse_bindings = (struct sc_mouse_bindings) {
|
opts->mouse_bindings.pri = forward;
|
||||||
.right_click = SC_MOUSE_BINDING_CLICK,
|
opts->mouse_bindings.sec = default_shortcuts;
|
||||||
.middle_click = SC_MOUSE_BINDING_CLICK,
|
|
||||||
.click4 = SC_MOUSE_BINDING_CLICK,
|
|
||||||
.click5 = SC_MOUSE_BINDING_CLICK,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2734,9 +2864,17 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum sc_gamepad_input_mode gmode = opts->gamepad_input_mode;
|
||||||
|
if (gmode != SC_GAMEPAD_INPUT_MODE_AOA
|
||||||
|
&& gmode != SC_GAMEPAD_INPUT_MODE_DISABLED) {
|
||||||
|
LOGE("In OTG mode, --gamepad only supports aoa or disabled.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (kmode == SC_KEYBOARD_INPUT_MODE_DISABLED
|
if (kmode == SC_KEYBOARD_INPUT_MODE_DISABLED
|
||||||
&& mmode == SC_MOUSE_INPUT_MODE_DISABLED) {
|
&& mmode == SC_MOUSE_INPUT_MODE_DISABLED
|
||||||
LOGE("Could not disable both keyboard and mouse in OTG mode.");
|
&& gmode == SC_GAMEPAD_INPUT_MODE_DISABLED) {
|
||||||
|
LOGE("Cannot not disable all inputs in OTG mode.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2758,6 +2896,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (opts->mouse_input_mode != SC_MOUSE_INPUT_MODE_SDK
|
||||||
|
&& !opts->mouse_hover) {
|
||||||
|
LOGE("--no-mouse-over is specific to --mouse=sdk");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if ((opts->tunnel_host || opts->tunnel_port) && !opts->force_adb_forward) {
|
if ((opts->tunnel_host || opts->tunnel_port) && !opts->force_adb_forward) {
|
||||||
LOGI("Tunnel host/port is set, "
|
LOGI("Tunnel host/port is set, "
|
||||||
"--force-adb-forward automatically enabled.");
|
"--force-adb-forward automatically enabled.");
|
||||||
@ -2771,18 +2915,18 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (opts->camera_id && opts->camera_facing != SC_CAMERA_FACING_ANY) {
|
if (opts->camera_id && opts->camera_facing != SC_CAMERA_FACING_ANY) {
|
||||||
LOGE("Could not specify both --camera-id and --camera-facing");
|
LOGE("Cannot specify both --camera-id and --camera-facing");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts->camera_size) {
|
if (opts->camera_size) {
|
||||||
if (opts->max_size) {
|
if (opts->max_size) {
|
||||||
LOGE("Could not specify both --camera-size and -m/--max-size");
|
LOGE("Cannot specify both --camera-size and -m/--max-size");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts->camera_ar) {
|
if (opts->camera_ar) {
|
||||||
LOGE("Could not specify both --camera-size and --camera-ar");
|
LOGE("Cannot specify both --camera-size and --camera-ar");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2809,13 +2953,31 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
if (opts->audio && opts->audio_source == SC_AUDIO_SOURCE_AUTO) {
|
if (opts->audio && opts->audio_source == SC_AUDIO_SOURCE_AUTO) {
|
||||||
// Select the audio source according to the video source
|
// Select the audio source according to the video source
|
||||||
if (opts->video_source == SC_VIDEO_SOURCE_DISPLAY) {
|
if (opts->video_source == SC_VIDEO_SOURCE_DISPLAY) {
|
||||||
opts->audio_source = SC_AUDIO_SOURCE_OUTPUT;
|
if (opts->audio_dup) {
|
||||||
|
LOGI("Audio duplication enabled: audio source switched to "
|
||||||
|
"\"playback\"");
|
||||||
|
opts->audio_source = SC_AUDIO_SOURCE_PLAYBACK;
|
||||||
|
} else {
|
||||||
|
opts->audio_source = SC_AUDIO_SOURCE_OUTPUT;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
opts->audio_source = SC_AUDIO_SOURCE_MIC;
|
opts->audio_source = SC_AUDIO_SOURCE_MIC;
|
||||||
LOGI("Camera video source: microphone audio source selected");
|
LOGI("Camera video source: microphone audio source selected");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (opts->audio_dup) {
|
||||||
|
if (!opts->audio) {
|
||||||
|
LOGE("--audio-dup not supported if audio is disabled");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts->audio_source != SC_AUDIO_SOURCE_PLAYBACK) {
|
||||||
|
LOGE("--audio-dup is specific to --audio-source=playback");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (opts->record_format && !opts->record_filename) {
|
if (opts->record_format && !opts->record_filename) {
|
||||||
LOGE("Record format specified without recording");
|
LOGE("Record format specified without recording");
|
||||||
return false;
|
return false;
|
||||||
@ -2905,19 +3067,19 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
|
|
||||||
if (!opts->control) {
|
if (!opts->control) {
|
||||||
if (opts->turn_screen_off) {
|
if (opts->turn_screen_off) {
|
||||||
LOGE("Could not request to turn screen off if control is disabled");
|
LOGE("Cannot request to turn screen off if control is disabled");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (opts->stay_awake) {
|
if (opts->stay_awake) {
|
||||||
LOGE("Could not request to stay awake if control is disabled");
|
LOGE("Cannot request to stay awake if control is disabled");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (opts->show_touches) {
|
if (opts->show_touches) {
|
||||||
LOGE("Could not request to show touches if control is disabled");
|
LOGE("Cannot request to show touches if control is disabled");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (opts->power_off_on_close) {
|
if (opts->power_off_on_close) {
|
||||||
LOGE("Could not request power off on close if control is disabled");
|
LOGE("Cannot request power off on close if control is disabled");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2942,7 +3104,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
// OTG mode is compatible with only very few options.
|
// OTG mode is compatible with only very few options.
|
||||||
// Only report obvious errors.
|
// Only report obvious errors.
|
||||||
if (opts->record_filename) {
|
if (opts->record_filename) {
|
||||||
LOGE("OTG mode: could not record");
|
LOGE("OTG mode: cannot record");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (opts->turn_screen_off) {
|
if (opts->turn_screen_off) {
|
||||||
@ -2997,7 +3159,7 @@ sc_get_pause_on_exit(int argc, char *argv[]) {
|
|||||||
if (!strcmp(value, "if-error")) {
|
if (!strcmp(value, "if-error")) {
|
||||||
return SC_PAUSE_ON_EXIT_IF_ERROR;
|
return SC_PAUSE_ON_EXIT_IF_ERROR;
|
||||||
}
|
}
|
||||||
// Set to false, inclusing when the value is invalid
|
// Set to false, including when the value is invalid
|
||||||
return SC_PAUSE_ON_EXIT_FALSE;
|
return SC_PAUSE_ON_EXIT_FALSE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
#define SC_CLOCK_NDEBUG // comment to debug
|
//#define SC_CLOCK_DEBUG // uncomment to debug
|
||||||
|
|
||||||
#define SC_CLOCK_RANGE 32
|
#define SC_CLOCK_RANGE 32
|
||||||
|
|
||||||
@ -21,10 +21,12 @@ sc_clock_update(struct sc_clock *clock, sc_tick system, sc_tick stream) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sc_tick offset = system - stream;
|
sc_tick offset = system - stream;
|
||||||
clock->offset = ((clock->range - 1) * clock->offset + offset)
|
unsigned clock_weight = clock->range - 1;
|
||||||
/ clock->range;
|
unsigned value_weight = SC_CLOCK_RANGE - clock->range + 1;
|
||||||
|
clock->offset = (clock->offset * clock_weight + offset * value_weight)
|
||||||
|
/ SC_CLOCK_RANGE;
|
||||||
|
|
||||||
#ifndef SC_CLOCK_NDEBUG
|
#ifdef SC_CLOCK_DEBUG
|
||||||
LOGD("Clock estimation: pts + %" PRItick, clock->offset);
|
LOGD("Clock estimation: pts + %" PRItick, clock->offset);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
#include <libavutil/version.h>
|
#include <libavutil/version.h>
|
||||||
#include <SDL2/SDL_version.h>
|
#include <SDL2/SDL_version.h>
|
||||||
|
|
||||||
#ifndef __WIN32
|
#ifndef _WIN32
|
||||||
# define PRIu64_ PRIu64
|
# define PRIu64_ PRIu64
|
||||||
# define SC_PRIsizet "zu"
|
# define SC_PRIsizet "zu"
|
||||||
#else
|
#else
|
||||||
|
@ -64,13 +64,11 @@ static const char *const copy_key_labels[] = {
|
|||||||
static inline const char *
|
static inline const char *
|
||||||
get_well_known_pointer_id_name(uint64_t pointer_id) {
|
get_well_known_pointer_id_name(uint64_t pointer_id) {
|
||||||
switch (pointer_id) {
|
switch (pointer_id) {
|
||||||
case POINTER_ID_MOUSE:
|
case SC_POINTER_ID_MOUSE:
|
||||||
return "mouse";
|
return "mouse";
|
||||||
case POINTER_ID_GENERIC_FINGER:
|
case SC_POINTER_ID_GENERIC_FINGER:
|
||||||
return "finger";
|
return "finger";
|
||||||
case POINTER_ID_VIRTUAL_MOUSE:
|
case SC_POINTER_ID_VIRTUAL_FINGER:
|
||||||
return "vmouse";
|
|
||||||
case POINTER_ID_VIRTUAL_FINGER:
|
|
||||||
return "vfinger";
|
return "vfinger";
|
||||||
default:
|
default:
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -85,15 +83,34 @@ write_position(uint8_t *buf, const struct sc_position *position) {
|
|||||||
sc_write16be(&buf[10], position->screen_size.height);
|
sc_write16be(&buf[10], position->screen_size.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
// write length (4 bytes) + string (non null-terminated)
|
// Write truncated string, and return the size
|
||||||
static size_t
|
static size_t
|
||||||
write_string(const char *utf8, size_t max_len, uint8_t *buf) {
|
write_string_payload(uint8_t *payload, const char *utf8, size_t max_len) {
|
||||||
|
if (!utf8) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
size_t len = sc_str_utf8_truncation_index(utf8, max_len);
|
size_t len = sc_str_utf8_truncation_index(utf8, max_len);
|
||||||
|
memcpy(payload, utf8, len);
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write length (4 bytes) + string (non null-terminated)
|
||||||
|
static size_t
|
||||||
|
write_string(uint8_t *buf, const char *utf8, size_t max_len) {
|
||||||
|
size_t len = write_string_payload(buf + 4, utf8, max_len);
|
||||||
sc_write32be(buf, len);
|
sc_write32be(buf, len);
|
||||||
memcpy(&buf[4], utf8, len);
|
|
||||||
return 4 + len;
|
return 4 + len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write length (1 byte) + string (non null-terminated)
|
||||||
|
static size_t
|
||||||
|
write_string_tiny(uint8_t *buf, const char *utf8, size_t max_len) {
|
||||||
|
assert(max_len <= 0xFF);
|
||||||
|
size_t len = write_string_payload(buf + 1, utf8, max_len);
|
||||||
|
buf[0] = len;
|
||||||
|
return 1 + len;
|
||||||
|
}
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
|
sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
|
||||||
buf[0] = msg->type;
|
buf[0] = msg->type;
|
||||||
@ -105,9 +122,8 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
|
|||||||
sc_write32be(&buf[10], msg->inject_keycode.metastate);
|
sc_write32be(&buf[10], msg->inject_keycode.metastate);
|
||||||
return 14;
|
return 14;
|
||||||
case SC_CONTROL_MSG_TYPE_INJECT_TEXT: {
|
case SC_CONTROL_MSG_TYPE_INJECT_TEXT: {
|
||||||
size_t len =
|
size_t len = write_string(&buf[1], msg->inject_text.text,
|
||||||
write_string(msg->inject_text.text,
|
SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
|
||||||
SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH, &buf[1]);
|
|
||||||
return 1 + len;
|
return 1 + len;
|
||||||
}
|
}
|
||||||
case SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT:
|
case SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT:
|
||||||
@ -139,24 +155,34 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
|
|||||||
case SC_CONTROL_MSG_TYPE_SET_CLIPBOARD:
|
case SC_CONTROL_MSG_TYPE_SET_CLIPBOARD:
|
||||||
sc_write64be(&buf[1], msg->set_clipboard.sequence);
|
sc_write64be(&buf[1], msg->set_clipboard.sequence);
|
||||||
buf[9] = !!msg->set_clipboard.paste;
|
buf[9] = !!msg->set_clipboard.paste;
|
||||||
size_t len = write_string(msg->set_clipboard.text,
|
size_t len = write_string(&buf[10], msg->set_clipboard.text,
|
||||||
SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH,
|
SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH);
|
||||||
&buf[10]);
|
|
||||||
return 10 + len;
|
return 10 + len;
|
||||||
case SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
case SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
||||||
buf[1] = msg->set_screen_power_mode.mode;
|
buf[1] = msg->set_screen_power_mode.mode;
|
||||||
return 2;
|
return 2;
|
||||||
case SC_CONTROL_MSG_TYPE_UHID_CREATE:
|
case SC_CONTROL_MSG_TYPE_UHID_CREATE:
|
||||||
sc_write16be(&buf[1], msg->uhid_create.id);
|
sc_write16be(&buf[1], msg->uhid_create.id);
|
||||||
sc_write16be(&buf[3], msg->uhid_create.report_desc_size);
|
|
||||||
memcpy(&buf[5], msg->uhid_create.report_desc,
|
size_t index = 3;
|
||||||
msg->uhid_create.report_desc_size);
|
index += write_string_tiny(&buf[index], msg->uhid_create.name, 127);
|
||||||
return 5 + msg->uhid_create.report_desc_size;
|
|
||||||
|
sc_write16be(&buf[index], msg->uhid_create.report_desc_size);
|
||||||
|
index += 2;
|
||||||
|
|
||||||
|
memcpy(&buf[index], msg->uhid_create.report_desc,
|
||||||
|
msg->uhid_create.report_desc_size);
|
||||||
|
index += msg->uhid_create.report_desc_size;
|
||||||
|
|
||||||
|
return index;
|
||||||
case SC_CONTROL_MSG_TYPE_UHID_INPUT:
|
case SC_CONTROL_MSG_TYPE_UHID_INPUT:
|
||||||
sc_write16be(&buf[1], msg->uhid_input.id);
|
sc_write16be(&buf[1], msg->uhid_input.id);
|
||||||
sc_write16be(&buf[3], msg->uhid_input.size);
|
sc_write16be(&buf[3], msg->uhid_input.size);
|
||||||
memcpy(&buf[5], msg->uhid_input.data, msg->uhid_input.size);
|
memcpy(&buf[5], msg->uhid_input.data, msg->uhid_input.size);
|
||||||
return 5 + msg->uhid_input.size;
|
return 5 + msg->uhid_input.size;
|
||||||
|
case SC_CONTROL_MSG_TYPE_UHID_DESTROY:
|
||||||
|
sc_write16be(&buf[1], msg->uhid_destroy.id);
|
||||||
|
return 3;
|
||||||
case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||||
case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
|
case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
|
||||||
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
||||||
@ -254,10 +280,15 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
|
|||||||
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
||||||
LOG_CMSG("rotate device");
|
LOG_CMSG("rotate device");
|
||||||
break;
|
break;
|
||||||
case SC_CONTROL_MSG_TYPE_UHID_CREATE:
|
case SC_CONTROL_MSG_TYPE_UHID_CREATE: {
|
||||||
LOG_CMSG("UHID create [%" PRIu16 "] report_desc_size=%" PRIu16,
|
// Quote only if name is not null
|
||||||
msg->uhid_create.id, msg->uhid_create.report_desc_size);
|
const char *name = msg->uhid_create.name;
|
||||||
|
const char *quote = name ? "\"" : "";
|
||||||
|
LOG_CMSG("UHID create [%" PRIu16 "] name=%s%s%s "
|
||||||
|
"report_desc_size=%" PRIu16, msg->uhid_create.id,
|
||||||
|
quote, name, quote, msg->uhid_create.report_desc_size);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case SC_CONTROL_MSG_TYPE_UHID_INPUT: {
|
case SC_CONTROL_MSG_TYPE_UHID_INPUT: {
|
||||||
char *hex = sc_str_to_hex_string(msg->uhid_input.data,
|
char *hex = sc_str_to_hex_string(msg->uhid_input.data,
|
||||||
msg->uhid_input.size);
|
msg->uhid_input.size);
|
||||||
@ -271,6 +302,9 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case SC_CONTROL_MSG_TYPE_UHID_DESTROY:
|
||||||
|
LOG_CMSG("UHID destroy [%" PRIu16 "]", msg->uhid_destroy.id);
|
||||||
|
break;
|
||||||
case SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
case SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
||||||
LOG_CMSG("open hard keyboard settings");
|
LOG_CMSG("open hard keyboard settings");
|
||||||
break;
|
break;
|
||||||
@ -280,6 +314,16 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_control_msg_is_droppable(const struct sc_control_msg *msg) {
|
||||||
|
// Cannot drop UHID_CREATE messages, because it would cause all further
|
||||||
|
// UHID_INPUT messages for this device to be invalid.
|
||||||
|
// Cannot drop UHID_DESTROY messages either, because a further UHID_CREATE
|
||||||
|
// with the same id may fail.
|
||||||
|
return msg->type != SC_CONTROL_MSG_TYPE_UHID_CREATE
|
||||||
|
&& msg->type != SC_CONTROL_MSG_TYPE_UHID_DESTROY;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_control_msg_destroy(struct sc_control_msg *msg) {
|
sc_control_msg_destroy(struct sc_control_msg *msg) {
|
||||||
switch (msg->type) {
|
switch (msg->type) {
|
||||||
|
@ -18,12 +18,11 @@
|
|||||||
// type: 1 byte; sequence: 8 bytes; paste flag: 1 byte; length: 4 bytes
|
// type: 1 byte; sequence: 8 bytes; paste flag: 1 byte; length: 4 bytes
|
||||||
#define SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (SC_CONTROL_MSG_MAX_SIZE - 14)
|
#define SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (SC_CONTROL_MSG_MAX_SIZE - 14)
|
||||||
|
|
||||||
#define POINTER_ID_MOUSE UINT64_C(-1)
|
#define SC_POINTER_ID_MOUSE UINT64_C(-1)
|
||||||
#define POINTER_ID_GENERIC_FINGER UINT64_C(-2)
|
#define SC_POINTER_ID_GENERIC_FINGER UINT64_C(-2)
|
||||||
|
|
||||||
// Used for injecting an additional virtual pointer for pinch-to-zoom
|
// Used for injecting an additional virtual pointer for pinch-to-zoom
|
||||||
#define POINTER_ID_VIRTUAL_MOUSE UINT64_C(-3)
|
#define SC_POINTER_ID_VIRTUAL_FINGER UINT64_C(-3)
|
||||||
#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-4)
|
|
||||||
|
|
||||||
enum sc_control_msg_type {
|
enum sc_control_msg_type {
|
||||||
SC_CONTROL_MSG_TYPE_INJECT_KEYCODE,
|
SC_CONTROL_MSG_TYPE_INJECT_KEYCODE,
|
||||||
@ -40,6 +39,7 @@ enum sc_control_msg_type {
|
|||||||
SC_CONTROL_MSG_TYPE_ROTATE_DEVICE,
|
SC_CONTROL_MSG_TYPE_ROTATE_DEVICE,
|
||||||
SC_CONTROL_MSG_TYPE_UHID_CREATE,
|
SC_CONTROL_MSG_TYPE_UHID_CREATE,
|
||||||
SC_CONTROL_MSG_TYPE_UHID_INPUT,
|
SC_CONTROL_MSG_TYPE_UHID_INPUT,
|
||||||
|
SC_CONTROL_MSG_TYPE_UHID_DESTROY,
|
||||||
SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS,
|
SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -98,6 +98,7 @@ struct sc_control_msg {
|
|||||||
} set_screen_power_mode;
|
} set_screen_power_mode;
|
||||||
struct {
|
struct {
|
||||||
uint16_t id;
|
uint16_t id;
|
||||||
|
const char *name; // pointer to static data
|
||||||
uint16_t report_desc_size;
|
uint16_t report_desc_size;
|
||||||
const uint8_t *report_desc; // pointer to static data
|
const uint8_t *report_desc; // pointer to static data
|
||||||
} uhid_create;
|
} uhid_create;
|
||||||
@ -106,6 +107,9 @@ struct sc_control_msg {
|
|||||||
uint16_t size;
|
uint16_t size;
|
||||||
uint8_t data[SC_HID_MAX_SIZE];
|
uint8_t data[SC_HID_MAX_SIZE];
|
||||||
} uhid_input;
|
} uhid_input;
|
||||||
|
struct {
|
||||||
|
uint16_t id;
|
||||||
|
} uhid_destroy;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -117,6 +121,11 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf);
|
|||||||
void
|
void
|
||||||
sc_control_msg_log(const struct sc_control_msg *msg);
|
sc_control_msg_log(const struct sc_control_msg *msg);
|
||||||
|
|
||||||
|
// Even when the buffer is "full", some messages must absolutely not be dropped
|
||||||
|
// to avoid inconsistencies.
|
||||||
|
bool
|
||||||
|
sc_control_msg_is_droppable(const struct sc_control_msg *msg);
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_control_msg_destroy(struct sc_control_msg *msg);
|
sc_control_msg_destroy(struct sc_control_msg *msg);
|
||||||
|
|
||||||
|
@ -4,15 +4,17 @@
|
|||||||
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
#define SC_CONTROL_MSG_QUEUE_MAX 64
|
// Drop droppable events above this limit
|
||||||
|
#define SC_CONTROL_MSG_QUEUE_LIMIT 60
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_controller_receiver_on_error(struct sc_receiver *receiver, void *userdata) {
|
sc_controller_receiver_on_ended(struct sc_receiver *receiver, bool error,
|
||||||
|
void *userdata) {
|
||||||
(void) receiver;
|
(void) receiver;
|
||||||
|
|
||||||
struct sc_controller *controller = userdata;
|
struct sc_controller *controller = userdata;
|
||||||
// Forward the event to the controller listener
|
// Forward the event to the controller listener
|
||||||
controller->cbs->on_error(controller, controller->cbs_userdata);
|
controller->cbs->on_ended(controller, error, controller->cbs_userdata);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
@ -21,13 +23,15 @@ sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
|
|||||||
void *cbs_userdata) {
|
void *cbs_userdata) {
|
||||||
sc_vecdeque_init(&controller->queue);
|
sc_vecdeque_init(&controller->queue);
|
||||||
|
|
||||||
bool ok = sc_vecdeque_reserve(&controller->queue, SC_CONTROL_MSG_QUEUE_MAX);
|
// Add 4 to support 4 non-droppable events without re-allocation
|
||||||
|
bool ok = sc_vecdeque_reserve(&controller->queue,
|
||||||
|
SC_CONTROL_MSG_QUEUE_LIMIT + 4);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct sc_receiver_callbacks receiver_cbs = {
|
static const struct sc_receiver_callbacks receiver_cbs = {
|
||||||
.on_error = sc_controller_receiver_on_error,
|
.on_ended = sc_controller_receiver_on_ended,
|
||||||
};
|
};
|
||||||
|
|
||||||
ok = sc_receiver_init(&controller->receiver, control_socket, &receiver_cbs,
|
ok = sc_receiver_init(&controller->receiver, control_socket, &receiver_cbs,
|
||||||
@ -55,7 +59,7 @@ sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
|
|||||||
controller->control_socket = control_socket;
|
controller->control_socket = control_socket;
|
||||||
controller->stopped = false;
|
controller->stopped = false;
|
||||||
|
|
||||||
assert(cbs && cbs->on_error);
|
assert(cbs && cbs->on_ended);
|
||||||
controller->cbs = cbs;
|
controller->cbs = cbs;
|
||||||
controller->cbs_userdata = cbs_userdata;
|
controller->cbs_userdata = cbs_userdata;
|
||||||
|
|
||||||
@ -92,39 +96,59 @@ sc_controller_push_msg(struct sc_controller *controller,
|
|||||||
sc_control_msg_log(msg);
|
sc_control_msg_log(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool pushed = false;
|
||||||
|
|
||||||
sc_mutex_lock(&controller->mutex);
|
sc_mutex_lock(&controller->mutex);
|
||||||
bool full = sc_vecdeque_is_full(&controller->queue);
|
size_t size = sc_vecdeque_size(&controller->queue);
|
||||||
if (!full) {
|
if (size < SC_CONTROL_MSG_QUEUE_LIMIT) {
|
||||||
bool was_empty = sc_vecdeque_is_empty(&controller->queue);
|
bool was_empty = sc_vecdeque_is_empty(&controller->queue);
|
||||||
sc_vecdeque_push_noresize(&controller->queue, *msg);
|
sc_vecdeque_push_noresize(&controller->queue, *msg);
|
||||||
|
pushed = true;
|
||||||
if (was_empty) {
|
if (was_empty) {
|
||||||
sc_cond_signal(&controller->msg_cond);
|
sc_cond_signal(&controller->msg_cond);
|
||||||
}
|
}
|
||||||
|
} else if (!sc_control_msg_is_droppable(msg)) {
|
||||||
|
bool ok = sc_vecdeque_push(&controller->queue, *msg);
|
||||||
|
if (ok) {
|
||||||
|
pushed = true;
|
||||||
|
} else {
|
||||||
|
// A non-droppable event must be dropped anyway
|
||||||
|
LOG_OOM();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Otherwise (if the queue is full), the msg is discarded
|
// Otherwise, the msg is discarded
|
||||||
|
|
||||||
sc_mutex_unlock(&controller->mutex);
|
sc_mutex_unlock(&controller->mutex);
|
||||||
|
|
||||||
return !full;
|
return pushed;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
process_msg(struct sc_controller *controller,
|
process_msg(struct sc_controller *controller,
|
||||||
const struct sc_control_msg *msg) {
|
const struct sc_control_msg *msg, bool *eos) {
|
||||||
static uint8_t serialized_msg[SC_CONTROL_MSG_MAX_SIZE];
|
static uint8_t serialized_msg[SC_CONTROL_MSG_MAX_SIZE];
|
||||||
size_t length = sc_control_msg_serialize(msg, serialized_msg);
|
size_t length = sc_control_msg_serialize(msg, serialized_msg);
|
||||||
if (!length) {
|
if (!length) {
|
||||||
|
*eos = false;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t w =
|
ssize_t w =
|
||||||
net_send_all(controller->control_socket, serialized_msg, length);
|
net_send_all(controller->control_socket, serialized_msg, length);
|
||||||
return (size_t) w == length;
|
if ((size_t) w != length) {
|
||||||
|
*eos = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
run_controller(void *data) {
|
run_controller(void *data) {
|
||||||
struct sc_controller *controller = data;
|
struct sc_controller *controller = data;
|
||||||
|
|
||||||
|
bool error = false;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
sc_mutex_lock(&controller->mutex);
|
sc_mutex_lock(&controller->mutex);
|
||||||
while (!controller->stopped
|
while (!controller->stopped
|
||||||
@ -134,6 +158,7 @@ run_controller(void *data) {
|
|||||||
if (controller->stopped) {
|
if (controller->stopped) {
|
||||||
// stop immediately, do not process further msgs
|
// stop immediately, do not process further msgs
|
||||||
sc_mutex_unlock(&controller->mutex);
|
sc_mutex_unlock(&controller->mutex);
|
||||||
|
LOGD("Controller stopped");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,20 +166,21 @@ run_controller(void *data) {
|
|||||||
struct sc_control_msg msg = sc_vecdeque_pop(&controller->queue);
|
struct sc_control_msg msg = sc_vecdeque_pop(&controller->queue);
|
||||||
sc_mutex_unlock(&controller->mutex);
|
sc_mutex_unlock(&controller->mutex);
|
||||||
|
|
||||||
bool ok = process_msg(controller, &msg);
|
bool eos;
|
||||||
|
bool ok = process_msg(controller, &msg, &eos);
|
||||||
sc_control_msg_destroy(&msg);
|
sc_control_msg_destroy(&msg);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGD("Could not write msg to socket");
|
if (eos) {
|
||||||
goto error;
|
LOGD("Controller stopped (socket closed)");
|
||||||
|
} // else error already logged
|
||||||
|
error = !eos;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
controller->cbs->on_ended(controller, error, controller->cbs_userdata);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
error:
|
|
||||||
controller->cbs->on_error(controller, controller->cbs_userdata);
|
|
||||||
|
|
||||||
return 1; // ignored
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
@ -28,7 +28,8 @@ struct sc_controller {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct sc_controller_callbacks {
|
struct sc_controller_callbacks {
|
||||||
void (*on_error)(struct sc_controller *controller, void *userdata);
|
void (*on_ended)(struct sc_controller *controller, bool error,
|
||||||
|
void *userdata);
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
@ -8,8 +8,6 @@
|
|||||||
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
#define SC_BUFFERING_NDEBUG // comment to debug
|
|
||||||
|
|
||||||
/** Downcast frame_sink to sc_delay_buffer */
|
/** Downcast frame_sink to sc_delay_buffer */
|
||||||
#define DOWNCAST(SINK) container_of(SINK, struct sc_delay_buffer, frame_sink)
|
#define DOWNCAST(SINK) container_of(SINK, struct sc_delay_buffer, frame_sink)
|
||||||
|
|
||||||
@ -80,7 +78,7 @@ run_buffering(void *data) {
|
|||||||
goto stopped;
|
goto stopped;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef SC_BUFFERING_NDEBUG
|
#ifdef SC_BUFFERING_DEBUG
|
||||||
LOGD("Buffering: %" PRItick ";%" PRItick ";%" PRItick,
|
LOGD("Buffering: %" PRItick ";%" PRItick ";%" PRItick,
|
||||||
pts, dframe.push_date, sc_tick_now());
|
pts, dframe.push_date, sc_tick_now());
|
||||||
#endif
|
#endif
|
||||||
@ -134,6 +132,7 @@ sc_delay_buffer_frame_sink_open(struct sc_frame_sink *sink,
|
|||||||
|
|
||||||
sc_clock_init(&db->clock);
|
sc_clock_init(&db->clock);
|
||||||
sc_vecdeque_init(&db->queue);
|
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)) {
|
||||||
goto error_destroy_wait_cond;
|
goto error_destroy_wait_cond;
|
||||||
@ -206,7 +205,7 @@ sc_delay_buffer_frame_sink_push(struct sc_frame_sink *sink,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef SC_BUFFERING_NDEBUG
|
#ifdef SC_BUFFERING_DEBUG
|
||||||
dframe.push_date = sc_tick_now();
|
dframe.push_date = sc_tick_now();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -12,12 +12,14 @@
|
|||||||
#include "util/tick.h"
|
#include "util/tick.h"
|
||||||
#include "util/vecdeque.h"
|
#include "util/vecdeque.h"
|
||||||
|
|
||||||
|
//#define SC_BUFFERING_DEBUG // uncomment to debug
|
||||||
|
|
||||||
// forward declarations
|
// forward declarations
|
||||||
typedef struct AVFrame AVFrame;
|
typedef struct AVFrame AVFrame;
|
||||||
|
|
||||||
struct sc_delayed_frame {
|
struct sc_delayed_frame {
|
||||||
AVFrame *frame;
|
AVFrame *frame;
|
||||||
#ifndef NDEBUG
|
#ifdef SC_BUFFERING_DEBUG
|
||||||
sc_tick push_date;
|
sc_tick push_date;
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
@ -278,7 +278,6 @@ run_demuxer(void *data) {
|
|||||||
finally_close_sinks:
|
finally_close_sinks:
|
||||||
sc_packet_source_sinks_close(&demuxer->packet_source);
|
sc_packet_source_sinks_close(&demuxer->packet_source);
|
||||||
finally_free_context:
|
finally_free_context:
|
||||||
// This also calls avcodec_close() internally
|
|
||||||
avcodec_free_context(&codec_ctx);
|
avcodec_free_context(&codec_ctx);
|
||||||
end:
|
end:
|
||||||
demuxer->cbs->on_ended(demuxer, status, demuxer->cbs_userdata);
|
demuxer->cbs->on_ended(demuxer, status, demuxer->cbs_userdata);
|
||||||
|
@ -43,6 +43,10 @@ sc_display_init(struct sc_display *display, SDL_Window *window,
|
|||||||
|
|
||||||
display->mipmaps = false;
|
display->mipmaps = false;
|
||||||
|
|
||||||
|
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||||
|
display->gl_context = NULL;
|
||||||
|
#endif
|
||||||
|
|
||||||
// starts with "opengl"
|
// starts with "opengl"
|
||||||
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
|
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
|
||||||
if (use_opengl) {
|
if (use_opengl) {
|
||||||
|
66
app/src/events.c
Normal file
66
app/src/events.c
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
#include "events.h"
|
||||||
|
|
||||||
|
#include "util/log.h"
|
||||||
|
#include "util/thread.h"
|
||||||
|
|
||||||
|
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) {
|
||||||
|
LOGE("Could not post %s event: %s", name, SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_post_to_main_thread(sc_runnable_fn run, void *userdata) {
|
||||||
|
SDL_Event event = {
|
||||||
|
.user = {
|
||||||
|
.type = SC_EVENT_RUN_ON_MAIN_THREAD,
|
||||||
|
.data1 = run,
|
||||||
|
.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());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int 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 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_reject_new_runnables(void) {
|
||||||
|
assert(sc_thread_get_id() == SC_MAIN_THREAD_ID);
|
||||||
|
|
||||||
|
SDL_SetEventFilter(task_event_filter, NULL);
|
||||||
|
}
|
@ -1,10 +1,38 @@
|
|||||||
#define SC_EVENT_NEW_FRAME SDL_USEREVENT
|
#ifndef SC_EVENTS_H
|
||||||
#define SC_EVENT_DEVICE_DISCONNECTED (SDL_USEREVENT + 1)
|
#define SC_EVENTS_H
|
||||||
#define SC_EVENT_SERVER_CONNECTION_FAILED (SDL_USEREVENT + 2)
|
|
||||||
#define SC_EVENT_SERVER_CONNECTED (SDL_USEREVENT + 3)
|
#include "common.h"
|
||||||
#define SC_EVENT_USB_DEVICE_DISCONNECTED (SDL_USEREVENT + 4)
|
|
||||||
#define SC_EVENT_DEMUXER_ERROR (SDL_USEREVENT + 5)
|
#include <stdbool.h>
|
||||||
#define SC_EVENT_RECORDER_ERROR (SDL_USEREVENT + 6)
|
#include <stdint.h>
|
||||||
#define SC_EVENT_SCREEN_INIT_SIZE (SDL_USEREVENT + 7)
|
#include <SDL_events.h>
|
||||||
#define SC_EVENT_TIME_LIMIT_REACHED (SDL_USEREVENT + 8)
|
|
||||||
#define SC_EVENT_CONTROLLER_ERROR (SDL_USEREVENT + 9)
|
enum {
|
||||||
|
SC_EVENT_NEW_FRAME = SDL_USEREVENT,
|
||||||
|
SC_EVENT_RUN_ON_MAIN_THREAD,
|
||||||
|
SC_EVENT_DEVICE_DISCONNECTED,
|
||||||
|
SC_EVENT_SERVER_CONNECTION_FAILED,
|
||||||
|
SC_EVENT_SERVER_CONNECTED,
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_push_event_impl(uint32_t type, const char *name);
|
||||||
|
|
||||||
|
#define sc_push_event(TYPE) sc_push_event_impl(TYPE, # TYPE)
|
||||||
|
|
||||||
|
typedef void (*sc_runnable_fn)(void *userdata);
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_post_to_main_thread(sc_runnable_fn run, void *userdata);
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_reject_new_runnables(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
@ -5,11 +5,23 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#define SC_HID_MAX_SIZE 8
|
#define SC_HID_MAX_SIZE 15
|
||||||
|
|
||||||
struct sc_hid_event {
|
struct sc_hid_input {
|
||||||
|
uint16_t hid_id;
|
||||||
uint8_t data[SC_HID_MAX_SIZE];
|
uint8_t data[SC_HID_MAX_SIZE];
|
||||||
uint8_t size;
|
uint8_t size;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct sc_hid_open {
|
||||||
|
uint16_t hid_id;
|
||||||
|
const char *name; // pointer to static memory
|
||||||
|
const uint8_t *report_desc; // pointer to static memory
|
||||||
|
size_t report_desc_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sc_hid_close {
|
||||||
|
uint16_t hid_id;
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
457
app/src/hid/hid_gamepad.c
Normal file
457
app/src/hid/hid_gamepad.c
Normal file
@ -0,0 +1,457 @@
|
|||||||
|
#include "hid_gamepad.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
#include "util/binary.h"
|
||||||
|
#include "util/log.h"
|
||||||
|
|
||||||
|
// 2x2 bytes for left stick (X, Y)
|
||||||
|
// 2x2 bytes for right stick (Z, Rz)
|
||||||
|
// 2x2 bytes for L2/R2 triggers
|
||||||
|
// 2 bytes for buttons + padding,
|
||||||
|
// 1 byte for hat switch (dpad) + padding
|
||||||
|
#define SC_HID_GAMEPAD_EVENT_SIZE 15
|
||||||
|
|
||||||
|
// The ->buttons field stores the state for all buttons, but only some of them
|
||||||
|
// (the 16 LSB) must be transmitted "as is". The DPAD (hat switch) buttons are
|
||||||
|
// stored locally in the MSB of this field, but not transmitted as is: they are
|
||||||
|
// transformed to generate another specific byte.
|
||||||
|
#define SC_HID_BUTTONS_MASK 0xFFFF
|
||||||
|
|
||||||
|
// outside SC_HID_BUTTONS_MASK
|
||||||
|
#define SC_GAMEPAD_BUTTONS_BIT_DPAD_UP UINT32_C(0x10000)
|
||||||
|
#define SC_GAMEPAD_BUTTONS_BIT_DPAD_DOWN UINT32_C(0x20000)
|
||||||
|
#define SC_GAMEPAD_BUTTONS_BIT_DPAD_LEFT UINT32_C(0x40000)
|
||||||
|
#define SC_GAMEPAD_BUTTONS_BIT_DPAD_RIGHT UINT32_C(0x80000)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gamepad descriptor manually crafted to transmit the input reports.
|
||||||
|
*
|
||||||
|
* The HID specification is available here:
|
||||||
|
* <https://www.usb.org/document-library/device-class-definition-hid-111>
|
||||||
|
*
|
||||||
|
* The HID Usage Tables is also useful:
|
||||||
|
* <https://www.usb.org/document-library/hid-usage-tables-15>
|
||||||
|
*/
|
||||||
|
static const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
|
||||||
|
// Usage Page (Generic Desktop)
|
||||||
|
0x05, 0x01,
|
||||||
|
// Usage (Gamepad)
|
||||||
|
0x09, 0x05,
|
||||||
|
|
||||||
|
// Collection (Application)
|
||||||
|
0xA1, 0x01,
|
||||||
|
|
||||||
|
// Collection (Physical)
|
||||||
|
0xA1, 0x00,
|
||||||
|
|
||||||
|
// Usage Page (Generic Desktop)
|
||||||
|
0x05, 0x01,
|
||||||
|
// Usage (X) Left stick x
|
||||||
|
0x09, 0x30,
|
||||||
|
// Usage (Y) Left stick y
|
||||||
|
0x09, 0x31,
|
||||||
|
// Usage (Z) Right stick x
|
||||||
|
0x09, 0x32,
|
||||||
|
// Usage (Rz) Right stick y
|
||||||
|
0x09, 0x35,
|
||||||
|
// Logical Minimum (0)
|
||||||
|
0x15, 0x00,
|
||||||
|
// Logical Maximum (65535)
|
||||||
|
// Cannot use 26 FF FF because 0xFFFF is interpreted as signed 16-bit
|
||||||
|
0x27, 0xFF, 0xFF, 0x00, 0x00, // little-endian
|
||||||
|
// Report Size (16)
|
||||||
|
0x75, 0x10,
|
||||||
|
// Report Count (4)
|
||||||
|
0x95, 0x04,
|
||||||
|
// Input (Data, Variable, Absolute): 4 bytes (X, Y, Z, Rz)
|
||||||
|
0x81, 0x02,
|
||||||
|
|
||||||
|
// Usage Page (Simulation Controls)
|
||||||
|
0x05, 0x02,
|
||||||
|
// Usage (Brake)
|
||||||
|
0x09, 0xC5,
|
||||||
|
// Usage (Accelerator)
|
||||||
|
0x09, 0xC4,
|
||||||
|
// Logical Minimum (0)
|
||||||
|
0x15, 0x00,
|
||||||
|
// Logical Maximum (32767)
|
||||||
|
0x26, 0xFF, 0x7F,
|
||||||
|
// Report Size (16)
|
||||||
|
0x75, 0x10,
|
||||||
|
// Report Count (2)
|
||||||
|
0x95, 0x02,
|
||||||
|
// Input (Data, Variable, Absolute): 2 bytes (L2, R2)
|
||||||
|
0x81, 0x02,
|
||||||
|
|
||||||
|
// Usage Page (Buttons)
|
||||||
|
0x05, 0x09,
|
||||||
|
// Usage Minimum (1)
|
||||||
|
0x19, 0x01,
|
||||||
|
// Usage Maximum (16)
|
||||||
|
0x29, 0x10,
|
||||||
|
// Logical Minimum (0)
|
||||||
|
0x15, 0x00,
|
||||||
|
// Logical Maximum (1)
|
||||||
|
0x25, 0x01,
|
||||||
|
// Report Count (16)
|
||||||
|
0x95, 0x10,
|
||||||
|
// Report Size (1)
|
||||||
|
0x75, 0x01,
|
||||||
|
// Input (Data, Variable, Absolute): 16 buttons bits
|
||||||
|
0x81, 0x02,
|
||||||
|
|
||||||
|
// Usage Page (Generic Desktop)
|
||||||
|
0x05, 0x01,
|
||||||
|
// Usage (Hat switch)
|
||||||
|
0x09, 0x39,
|
||||||
|
// Logical Minimum (1)
|
||||||
|
0x15, 0x01,
|
||||||
|
// Logical Maximum (8)
|
||||||
|
0x25, 0x08,
|
||||||
|
// Report Size (4)
|
||||||
|
0x75, 0x04,
|
||||||
|
// Report Count (1)
|
||||||
|
0x95, 0x01,
|
||||||
|
// Input (Data, Variable, Null State): 4-bit value
|
||||||
|
0x81, 0x42,
|
||||||
|
|
||||||
|
// End Collection
|
||||||
|
0xC0,
|
||||||
|
|
||||||
|
// End Collection
|
||||||
|
0xC0,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A gamepad HID input report is 15 bytes long:
|
||||||
|
* - bytes 0-3: left stick state
|
||||||
|
* - bytes 4-7: right stick state
|
||||||
|
* - bytes 8-11: L2/R2 triggers state
|
||||||
|
* - bytes 12-13: buttons state
|
||||||
|
* - bytes 14: hat switch position (dpad)
|
||||||
|
*
|
||||||
|
* +---------------+
|
||||||
|
* byte 0: |. . . . . . . .|
|
||||||
|
* | | left stick x (0-65535, little-endian)
|
||||||
|
* byte 1: |. . . . . . . .|
|
||||||
|
* +---------------+
|
||||||
|
* byte 2: |. . . . . . . .|
|
||||||
|
* | | left stick y (0-65535, little-endian)
|
||||||
|
* byte 3: |. . . . . . . .|
|
||||||
|
* +---------------+
|
||||||
|
* byte 4: |. . . . . . . .|
|
||||||
|
* | | right stick x (0-65535, little-endian)
|
||||||
|
* byte 5: |. . . . . . . .|
|
||||||
|
* +---------------+
|
||||||
|
* byte 6: |. . . . . . . .|
|
||||||
|
* | | right stick y (0-65535, little-endian)
|
||||||
|
* byte 7: |. . . . . . . .|
|
||||||
|
* +---------------+
|
||||||
|
* byte 8: |. . . . . . . .|
|
||||||
|
* | | L2 trigger (0-32767, little-endian)
|
||||||
|
* byte 9: |0 . . . . . . .|
|
||||||
|
* +---------------+
|
||||||
|
* byte 10: |. . . . . . . .|
|
||||||
|
* | | R2 trigger (0-32767, little-endian)
|
||||||
|
* byte 11: |0 . . . . . . .|
|
||||||
|
* +---------------+
|
||||||
|
*
|
||||||
|
* ,--------------- SC_GAMEPAD_BUTTON_RIGHT_SHOULDER
|
||||||
|
* | ,------------- SC_GAMEPAD_BUTTON_LEFT_SHOULDER
|
||||||
|
* | |
|
||||||
|
* | | ,--------- SC_GAMEPAD_BUTTON_NORTH
|
||||||
|
* | | | ,------- SC_GAMEPAD_BUTTON_WEST
|
||||||
|
* | | | |
|
||||||
|
* | | | | ,--- SC_GAMEPAD_BUTTON_EAST
|
||||||
|
* | | | | | ,- SC_GAMEPAD_BUTTON_SOUTH
|
||||||
|
* v v v v v v
|
||||||
|
* +---------------+
|
||||||
|
* byte 12: |. . 0 . . 0 . .|
|
||||||
|
* | | Buttons (16-bit little-endian)
|
||||||
|
* byte 13: |0 . . . . . 0 0|
|
||||||
|
* +---------------+
|
||||||
|
* ^ ^ ^ ^ ^
|
||||||
|
* | | | | |
|
||||||
|
* | | | | |
|
||||||
|
* | | | | `----- SC_GAMEPAD_BUTTON_BACK
|
||||||
|
* | | | `------- SC_GAMEPAD_BUTTON_START
|
||||||
|
* | | `--------- SC_GAMEPAD_BUTTON_GUIDE
|
||||||
|
* | `----------- SC_GAMEPAD_BUTTON_LEFT_STICK
|
||||||
|
* `------------- SC_GAMEPAD_BUTTON_RIGHT_STICK
|
||||||
|
*
|
||||||
|
* +---------------+
|
||||||
|
* byte 14: |0 0 0 . . . . .| hat switch (dpad) position (0-8)
|
||||||
|
* +---------------+
|
||||||
|
* 9 possible positions and their values:
|
||||||
|
* 8 1 2
|
||||||
|
* 7 0 3
|
||||||
|
* 6 5 4
|
||||||
|
* (8 is top-left, 1 is top, 2 is top-right, etc.)
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_hid_gamepad_slot_init(struct sc_hid_gamepad_slot *slot,
|
||||||
|
uint32_t gamepad_id) {
|
||||||
|
assert(gamepad_id != SC_GAMEPAD_ID_INVALID);
|
||||||
|
slot->gamepad_id = gamepad_id;
|
||||||
|
slot->buttons = 0;
|
||||||
|
slot->axis_left_x = 0;
|
||||||
|
slot->axis_left_y = 0;
|
||||||
|
slot->axis_right_x = 0;
|
||||||
|
slot->axis_right_y = 0;
|
||||||
|
slot->axis_left_trigger = 0;
|
||||||
|
slot->axis_right_trigger = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t
|
||||||
|
sc_hid_gamepad_slot_find(struct sc_hid_gamepad *hid, uint32_t gamepad_id) {
|
||||||
|
for (size_t i = 0; i < SC_MAX_GAMEPADS; ++i) {
|
||||||
|
if (gamepad_id == hid->slots[i].gamepad_id) {
|
||||||
|
// found
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_hid_gamepad_init(struct sc_hid_gamepad *hid) {
|
||||||
|
for (size_t i = 0; i < SC_MAX_GAMEPADS; ++i) {
|
||||||
|
hid->slots[i].gamepad_id = SC_GAMEPAD_ID_INVALID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint16_t
|
||||||
|
sc_hid_gamepad_slot_get_id(size_t slot_idx) {
|
||||||
|
assert(slot_idx < SC_MAX_GAMEPADS);
|
||||||
|
return SC_HID_ID_GAMEPAD_FIRST + slot_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_hid_gamepad_generate_open(struct sc_hid_gamepad *hid,
|
||||||
|
struct sc_hid_open *hid_open,
|
||||||
|
uint32_t gamepad_id) {
|
||||||
|
assert(gamepad_id != SC_GAMEPAD_ID_INVALID);
|
||||||
|
ssize_t slot_idx = sc_hid_gamepad_slot_find(hid, SC_GAMEPAD_ID_INVALID);
|
||||||
|
if (slot_idx == -1) {
|
||||||
|
LOGW("No gamepad slot available for new gamepad %" PRIu32, gamepad_id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_hid_gamepad_slot_init(&hid->slots[slot_idx], gamepad_id);
|
||||||
|
|
||||||
|
SDL_GameController* game_controller =
|
||||||
|
SDL_GameControllerFromInstanceID(gamepad_id);
|
||||||
|
assert(game_controller);
|
||||||
|
const char *name = SDL_GameControllerName(game_controller);
|
||||||
|
|
||||||
|
uint16_t hid_id = sc_hid_gamepad_slot_get_id(slot_idx);
|
||||||
|
hid_open->hid_id = hid_id;
|
||||||
|
hid_open->name = name;
|
||||||
|
hid_open->report_desc = SC_HID_GAMEPAD_REPORT_DESC;
|
||||||
|
hid_open->report_desc_size = sizeof(SC_HID_GAMEPAD_REPORT_DESC);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_hid_gamepad_generate_close(struct sc_hid_gamepad *hid,
|
||||||
|
struct sc_hid_close *hid_close,
|
||||||
|
uint32_t gamepad_id) {
|
||||||
|
assert(gamepad_id != SC_GAMEPAD_ID_INVALID);
|
||||||
|
ssize_t slot_idx = sc_hid_gamepad_slot_find(hid, gamepad_id);
|
||||||
|
if (slot_idx == -1) {
|
||||||
|
LOGW("Unknown gamepad removed %" PRIu32, gamepad_id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
hid->slots[slot_idx].gamepad_id = SC_GAMEPAD_ID_INVALID;
|
||||||
|
|
||||||
|
uint16_t hid_id = sc_hid_gamepad_slot_get_id(slot_idx);
|
||||||
|
hid_close->hid_id = hid_id;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t
|
||||||
|
sc_hid_gamepad_get_dpad_value(uint32_t buttons) {
|
||||||
|
// Value depending on direction:
|
||||||
|
// 8 1 2
|
||||||
|
// 7 0 3
|
||||||
|
// 6 5 4
|
||||||
|
if (buttons & SC_GAMEPAD_BUTTONS_BIT_DPAD_UP) {
|
||||||
|
if (buttons & SC_GAMEPAD_BUTTONS_BIT_DPAD_LEFT) {
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
if (buttons & SC_GAMEPAD_BUTTONS_BIT_DPAD_RIGHT) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (buttons & SC_GAMEPAD_BUTTONS_BIT_DPAD_DOWN) {
|
||||||
|
if (buttons & SC_GAMEPAD_BUTTONS_BIT_DPAD_LEFT) {
|
||||||
|
return 6;
|
||||||
|
}
|
||||||
|
if (buttons & SC_GAMEPAD_BUTTONS_BIT_DPAD_RIGHT) {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
if (buttons & SC_GAMEPAD_BUTTONS_BIT_DPAD_LEFT) {
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
|
if (buttons & SC_GAMEPAD_BUTTONS_BIT_DPAD_RIGHT) {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_hid_gamepad_event_from_slot(uint16_t hid_id,
|
||||||
|
const struct sc_hid_gamepad_slot *slot,
|
||||||
|
struct sc_hid_input *hid_input) {
|
||||||
|
hid_input->hid_id = hid_id;
|
||||||
|
hid_input->size = SC_HID_GAMEPAD_EVENT_SIZE;
|
||||||
|
|
||||||
|
uint8_t *data = hid_input->data;
|
||||||
|
// Values must be written in little-endian
|
||||||
|
sc_write16le(data, slot->axis_left_x);
|
||||||
|
sc_write16le(data + 2, slot->axis_left_y);
|
||||||
|
sc_write16le(data + 4, slot->axis_right_x);
|
||||||
|
sc_write16le(data + 6, slot->axis_right_y);
|
||||||
|
sc_write16le(data + 8, slot->axis_left_trigger);
|
||||||
|
sc_write16le(data + 10, slot->axis_right_trigger);
|
||||||
|
sc_write16le(data + 12, slot->buttons & SC_HID_BUTTONS_MASK);
|
||||||
|
data[14] = sc_hid_gamepad_get_dpad_value(slot->buttons);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t
|
||||||
|
sc_hid_gamepad_get_button_id(enum sc_gamepad_button button) {
|
||||||
|
switch (button) {
|
||||||
|
case SC_GAMEPAD_BUTTON_SOUTH:
|
||||||
|
return 0x0001;
|
||||||
|
case SC_GAMEPAD_BUTTON_EAST:
|
||||||
|
return 0x0002;
|
||||||
|
case SC_GAMEPAD_BUTTON_WEST:
|
||||||
|
return 0x0008;
|
||||||
|
case SC_GAMEPAD_BUTTON_NORTH:
|
||||||
|
return 0x0010;
|
||||||
|
case SC_GAMEPAD_BUTTON_BACK:
|
||||||
|
return 0x0400;
|
||||||
|
case SC_GAMEPAD_BUTTON_GUIDE:
|
||||||
|
return 0x1000;
|
||||||
|
case SC_GAMEPAD_BUTTON_START:
|
||||||
|
return 0x0800;
|
||||||
|
case SC_GAMEPAD_BUTTON_LEFT_STICK:
|
||||||
|
return 0x2000;
|
||||||
|
case SC_GAMEPAD_BUTTON_RIGHT_STICK:
|
||||||
|
return 0x4000;
|
||||||
|
case SC_GAMEPAD_BUTTON_LEFT_SHOULDER:
|
||||||
|
return 0x0040;
|
||||||
|
case SC_GAMEPAD_BUTTON_RIGHT_SHOULDER:
|
||||||
|
return 0x0080;
|
||||||
|
case SC_GAMEPAD_BUTTON_DPAD_UP:
|
||||||
|
return SC_GAMEPAD_BUTTONS_BIT_DPAD_UP;
|
||||||
|
case SC_GAMEPAD_BUTTON_DPAD_DOWN:
|
||||||
|
return SC_GAMEPAD_BUTTONS_BIT_DPAD_DOWN;
|
||||||
|
case SC_GAMEPAD_BUTTON_DPAD_LEFT:
|
||||||
|
return SC_GAMEPAD_BUTTONS_BIT_DPAD_LEFT;
|
||||||
|
case SC_GAMEPAD_BUTTON_DPAD_RIGHT:
|
||||||
|
return SC_GAMEPAD_BUTTONS_BIT_DPAD_RIGHT;
|
||||||
|
default:
|
||||||
|
// unknown button, ignore
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_hid_gamepad_generate_input_from_button(struct sc_hid_gamepad *hid,
|
||||||
|
struct sc_hid_input *hid_input,
|
||||||
|
const struct sc_gamepad_button_event *event) {
|
||||||
|
if ((event->button < 0) || (event->button > 15)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t gamepad_id = event->gamepad_id;
|
||||||
|
|
||||||
|
ssize_t slot_idx = sc_hid_gamepad_slot_find(hid, gamepad_id);
|
||||||
|
if (slot_idx == -1) {
|
||||||
|
LOGW("Axis event for unknown gamepad %" PRIu32, gamepad_id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(slot_idx < SC_MAX_GAMEPADS);
|
||||||
|
|
||||||
|
struct sc_hid_gamepad_slot *slot = &hid->slots[slot_idx];
|
||||||
|
|
||||||
|
uint32_t button = sc_hid_gamepad_get_button_id(event->button);
|
||||||
|
if (!button) {
|
||||||
|
// unknown button, ignore
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event->action == SC_ACTION_DOWN) {
|
||||||
|
slot->buttons |= button;
|
||||||
|
} else {
|
||||||
|
assert(event->action == SC_ACTION_UP);
|
||||||
|
slot->buttons &= ~button;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t hid_id = sc_hid_gamepad_slot_get_id(slot_idx);
|
||||||
|
sc_hid_gamepad_event_from_slot(hid_id, slot, hid_input);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_hid_gamepad_generate_input_from_axis(struct sc_hid_gamepad *hid,
|
||||||
|
struct sc_hid_input *hid_input,
|
||||||
|
const struct sc_gamepad_axis_event *event) {
|
||||||
|
uint32_t gamepad_id = event->gamepad_id;
|
||||||
|
|
||||||
|
ssize_t slot_idx = sc_hid_gamepad_slot_find(hid, gamepad_id);
|
||||||
|
if (slot_idx == -1) {
|
||||||
|
LOGW("Button event for unknown gamepad %" PRIu32, gamepad_id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(slot_idx < SC_MAX_GAMEPADS);
|
||||||
|
|
||||||
|
struct sc_hid_gamepad_slot *slot = &hid->slots[slot_idx];
|
||||||
|
|
||||||
|
// [-32768 to 32767] -> [0 to 65535]
|
||||||
|
#define AXIS_RESCALE(V) (uint16_t) (((int32_t) V) + 0x8000)
|
||||||
|
switch (event->axis) {
|
||||||
|
case SC_GAMEPAD_AXIS_LEFTX:
|
||||||
|
slot->axis_left_x = AXIS_RESCALE(event->value);
|
||||||
|
break;
|
||||||
|
case SC_GAMEPAD_AXIS_LEFTY:
|
||||||
|
slot->axis_left_y = AXIS_RESCALE(event->value);
|
||||||
|
break;
|
||||||
|
case SC_GAMEPAD_AXIS_RIGHTX:
|
||||||
|
slot->axis_right_x = AXIS_RESCALE(event->value);
|
||||||
|
break;
|
||||||
|
case SC_GAMEPAD_AXIS_RIGHTY:
|
||||||
|
slot->axis_right_y = AXIS_RESCALE(event->value);
|
||||||
|
break;
|
||||||
|
case SC_GAMEPAD_AXIS_LEFT_TRIGGER:
|
||||||
|
// Trigger is always positive between 0 and 32767
|
||||||
|
slot->axis_left_trigger = MAX(0, event->value);
|
||||||
|
break;
|
||||||
|
case SC_GAMEPAD_AXIS_RIGHT_TRIGGER:
|
||||||
|
// Trigger is always positive between 0 and 32767
|
||||||
|
slot->axis_right_trigger = MAX(0, event->value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t hid_id = sc_hid_gamepad_slot_get_id(slot_idx);
|
||||||
|
sc_hid_gamepad_event_from_slot(hid_id, slot, hid_input);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
53
app/src/hid/hid_gamepad.h
Normal file
53
app/src/hid/hid_gamepad.h
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#ifndef SC_HID_GAMEPAD_H
|
||||||
|
#define SC_HID_GAMEPAD_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "hid/hid_event.h"
|
||||||
|
#include "input_events.h"
|
||||||
|
|
||||||
|
#define SC_MAX_GAMEPADS 8
|
||||||
|
#define SC_HID_ID_GAMEPAD_FIRST 3
|
||||||
|
#define SC_HID_ID_GAMEPAD_LAST (SC_HID_ID_GAMEPAD_FIRST + SC_MAX_GAMEPADS - 1)
|
||||||
|
|
||||||
|
struct sc_hid_gamepad_slot {
|
||||||
|
uint32_t gamepad_id;
|
||||||
|
uint32_t buttons;
|
||||||
|
uint16_t axis_left_x;
|
||||||
|
uint16_t axis_left_y;
|
||||||
|
uint16_t axis_right_x;
|
||||||
|
uint16_t axis_right_y;
|
||||||
|
uint16_t axis_left_trigger;
|
||||||
|
uint16_t axis_right_trigger;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sc_hid_gamepad {
|
||||||
|
struct sc_hid_gamepad_slot slots[SC_MAX_GAMEPADS];
|
||||||
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_hid_gamepad_init(struct sc_hid_gamepad *hid);
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_hid_gamepad_generate_open(struct sc_hid_gamepad *hid,
|
||||||
|
struct sc_hid_open *hid_open,
|
||||||
|
uint32_t gamepad_id);
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_hid_gamepad_generate_close(struct sc_hid_gamepad *hid,
|
||||||
|
struct sc_hid_close *hid_close,
|
||||||
|
uint32_t gamepad_id);
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_hid_gamepad_generate_input_from_button(struct sc_hid_gamepad *hid,
|
||||||
|
struct sc_hid_input *hid_input,
|
||||||
|
const struct sc_gamepad_button_event *event);
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_hid_gamepad_generate_input_from_axis(struct sc_hid_gamepad *hid,
|
||||||
|
struct sc_hid_input *hid_input,
|
||||||
|
const struct sc_gamepad_axis_event *event);
|
||||||
|
|
||||||
|
#endif
|
@ -21,7 +21,7 @@
|
|||||||
// keyboard support, though OS could support more keys via modifying the report
|
// keyboard support, though OS could support more keys via modifying the report
|
||||||
// desc. 6 should be enough for scrcpy.
|
// desc. 6 should be enough for scrcpy.
|
||||||
#define SC_HID_KEYBOARD_MAX_KEYS 6
|
#define SC_HID_KEYBOARD_MAX_KEYS 6
|
||||||
#define SC_HID_KEYBOARD_EVENT_SIZE \
|
#define SC_HID_KEYBOARD_INPUT_SIZE \
|
||||||
(SC_HID_KEYBOARD_INDEX_KEYS + SC_HID_KEYBOARD_MAX_KEYS)
|
(SC_HID_KEYBOARD_INDEX_KEYS + SC_HID_KEYBOARD_MAX_KEYS)
|
||||||
|
|
||||||
#define SC_HID_RESERVED 0x00
|
#define SC_HID_RESERVED 0x00
|
||||||
@ -31,13 +31,16 @@
|
|||||||
* For HID, only report descriptor is needed.
|
* For HID, only report descriptor is needed.
|
||||||
*
|
*
|
||||||
* The specification is available here:
|
* The specification is available here:
|
||||||
* <https://www.usb.org/sites/default/files/hid1_11.pdf>
|
* <https://www.usb.org/document-library/device-class-definition-hid-111>
|
||||||
*
|
*
|
||||||
* In particular, read:
|
* In particular, read:
|
||||||
* - 6.2.2 Report Descriptor
|
* - §6.2.2 Report Descriptor
|
||||||
* - Appendix B.1 Protocol 1 (Keyboard)
|
* - Appendix B.1 Protocol 1 (Keyboard)
|
||||||
* - Appendix C: Keyboard Implementation
|
* - Appendix C: Keyboard Implementation
|
||||||
*
|
*
|
||||||
|
* The HID Usage Tables is also useful:
|
||||||
|
* <https://www.usb.org/document-library/hid-usage-tables-15>
|
||||||
|
*
|
||||||
* Normally a basic HID keyboard uses 8 bytes:
|
* Normally a basic HID keyboard uses 8 bytes:
|
||||||
* Modifier Reserved Key Key Key Key Key Key
|
* Modifier Reserved Key Key Key Key Key Key
|
||||||
*
|
*
|
||||||
@ -47,7 +50,7 @@
|
|||||||
*
|
*
|
||||||
* (change vid:pid' to your device's vendor ID and product ID).
|
* (change vid:pid' to your device's vendor ID and product ID).
|
||||||
*/
|
*/
|
||||||
const uint8_t SC_HID_KEYBOARD_REPORT_DESC[] = {
|
static const uint8_t SC_HID_KEYBOARD_REPORT_DESC[] = {
|
||||||
// Usage Page (Generic Desktop)
|
// Usage Page (Generic Desktop)
|
||||||
0x05, 0x01,
|
0x05, 0x01,
|
||||||
// Usage (Keyboard)
|
// Usage (Keyboard)
|
||||||
@ -60,7 +63,7 @@ const uint8_t SC_HID_KEYBOARD_REPORT_DESC[] = {
|
|||||||
0x05, 0x07,
|
0x05, 0x07,
|
||||||
// Usage Minimum (224)
|
// Usage Minimum (224)
|
||||||
0x19, 0xE0,
|
0x19, 0xE0,
|
||||||
// Usage Maximum (231)
|
// Usage Maximum (231)
|
||||||
0x29, 0xE7,
|
0x29, 0xE7,
|
||||||
// Logical Minimum (0)
|
// Logical Minimum (0)
|
||||||
0x15, 0x00,
|
0x15, 0x00,
|
||||||
@ -121,11 +124,8 @@ const uint8_t SC_HID_KEYBOARD_REPORT_DESC[] = {
|
|||||||
0xC0
|
0xC0
|
||||||
};
|
};
|
||||||
|
|
||||||
const size_t SC_HID_KEYBOARD_REPORT_DESC_LEN =
|
|
||||||
sizeof(SC_HID_KEYBOARD_REPORT_DESC);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A keyboard HID event is 8 bytes long:
|
* A keyboard HID input report is 8 bytes long:
|
||||||
*
|
*
|
||||||
* - byte 0: modifiers (1 flag per modifier key, 8 possible modifier keys)
|
* - byte 0: modifiers (1 flag per modifier key, 8 possible modifier keys)
|
||||||
* - byte 1: reserved (always 0)
|
* - byte 1: reserved (always 0)
|
||||||
@ -199,10 +199,11 @@ const size_t SC_HID_KEYBOARD_REPORT_DESC_LEN =
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_hid_keyboard_event_init(struct sc_hid_event *hid_event) {
|
sc_hid_keyboard_input_init(struct sc_hid_input *hid_input) {
|
||||||
hid_event->size = SC_HID_KEYBOARD_EVENT_SIZE;
|
hid_input->hid_id = SC_HID_ID_KEYBOARD;
|
||||||
|
hid_input->size = SC_HID_KEYBOARD_INPUT_SIZE;
|
||||||
|
|
||||||
uint8_t *data = hid_event->data;
|
uint8_t *data = hid_input->data;
|
||||||
|
|
||||||
data[SC_HID_KEYBOARD_INDEX_MODS] = SC_HID_MOD_NONE;
|
data[SC_HID_KEYBOARD_INDEX_MODS] = SC_HID_MOD_NONE;
|
||||||
data[1] = SC_HID_RESERVED;
|
data[1] = SC_HID_RESERVED;
|
||||||
@ -250,9 +251,9 @@ scancode_is_modifier(enum sc_scancode scancode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_hid_keyboard_event_from_key(struct sc_hid_keyboard *hid,
|
sc_hid_keyboard_generate_input_from_key(struct sc_hid_keyboard *hid,
|
||||||
struct sc_hid_event *hid_event,
|
struct sc_hid_input *hid_input,
|
||||||
const struct sc_key_event *event) {
|
const struct sc_key_event *event) {
|
||||||
enum sc_scancode scancode = event->scancode;
|
enum sc_scancode scancode = event->scancode;
|
||||||
assert(scancode >= 0);
|
assert(scancode >= 0);
|
||||||
|
|
||||||
@ -264,7 +265,7 @@ sc_hid_keyboard_event_from_key(struct sc_hid_keyboard *hid,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_hid_keyboard_event_init(hid_event);
|
sc_hid_keyboard_input_init(hid_input);
|
||||||
|
|
||||||
uint16_t mods = sc_hid_mod_from_sdl_keymod(event->mods_state);
|
uint16_t mods = sc_hid_mod_from_sdl_keymod(event->mods_state);
|
||||||
|
|
||||||
@ -275,9 +276,9 @@ sc_hid_keyboard_event_from_key(struct sc_hid_keyboard *hid,
|
|||||||
hid->keys[scancode] ? "true" : "false");
|
hid->keys[scancode] ? "true" : "false");
|
||||||
}
|
}
|
||||||
|
|
||||||
hid_event->data[SC_HID_KEYBOARD_INDEX_MODS] = mods;
|
hid_input->data[SC_HID_KEYBOARD_INDEX_MODS] = mods;
|
||||||
|
|
||||||
uint8_t *keys_data = &hid_event->data[SC_HID_KEYBOARD_INDEX_KEYS];
|
uint8_t *keys_data = &hid_input->data[SC_HID_KEYBOARD_INDEX_KEYS];
|
||||||
// Re-calculate pressed keys every time
|
// Re-calculate pressed keys every time
|
||||||
int keys_pressed_count = 0;
|
int keys_pressed_count = 0;
|
||||||
for (int i = 0; i < SC_HID_KEYBOARD_KEYS; ++i) {
|
for (int i = 0; i < SC_HID_KEYBOARD_KEYS; ++i) {
|
||||||
@ -308,8 +309,8 @@ end:
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_hid_keyboard_event_from_mods(struct sc_hid_event *event,
|
sc_hid_keyboard_generate_input_from_mods(struct sc_hid_input *hid_input,
|
||||||
uint16_t mods_state) {
|
uint16_t mods_state) {
|
||||||
bool capslock = mods_state & SC_MOD_CAPS;
|
bool capslock = mods_state & SC_MOD_CAPS;
|
||||||
bool numlock = mods_state & SC_MOD_NUM;
|
bool numlock = mods_state & SC_MOD_NUM;
|
||||||
if (!capslock && !numlock) {
|
if (!capslock && !numlock) {
|
||||||
@ -317,17 +318,28 @@ sc_hid_keyboard_event_from_mods(struct sc_hid_event *event,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_hid_keyboard_event_init(event);
|
sc_hid_keyboard_input_init(hid_input);
|
||||||
|
|
||||||
unsigned i = 0;
|
unsigned i = 0;
|
||||||
if (capslock) {
|
if (capslock) {
|
||||||
event->data[SC_HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_CAPSLOCK;
|
hid_input->data[SC_HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_CAPSLOCK;
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
if (numlock) {
|
if (numlock) {
|
||||||
event->data[SC_HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_NUMLOCK;
|
hid_input->data[SC_HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_NUMLOCK;
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sc_hid_keyboard_generate_open(struct sc_hid_open *hid_open) {
|
||||||
|
hid_open->hid_id = SC_HID_ID_KEYBOARD;
|
||||||
|
hid_open->name = NULL; // No name specified after "scrcpy"
|
||||||
|
hid_open->report_desc = SC_HID_KEYBOARD_REPORT_DESC;
|
||||||
|
hid_open->report_desc_size = sizeof(SC_HID_KEYBOARD_REPORT_DESC);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_hid_keyboard_generate_close(struct sc_hid_close *hid_close) {
|
||||||
|
hid_close->hid_id = SC_HID_ID_KEYBOARD;
|
||||||
|
}
|
||||||
|
@ -14,8 +14,7 @@
|
|||||||
// 0x65 is Application, typically AT-101 Keyboard ends here.
|
// 0x65 is Application, typically AT-101 Keyboard ends here.
|
||||||
#define SC_HID_KEYBOARD_KEYS 0x66
|
#define SC_HID_KEYBOARD_KEYS 0x66
|
||||||
|
|
||||||
extern const uint8_t SC_HID_KEYBOARD_REPORT_DESC[];
|
#define SC_HID_ID_KEYBOARD 1
|
||||||
extern const size_t SC_HID_KEYBOARD_REPORT_DESC_LEN;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HID keyboard events are sequence-based, every time keyboard state changes
|
* HID keyboard events are sequence-based, every time keyboard state changes
|
||||||
@ -36,13 +35,19 @@ struct sc_hid_keyboard {
|
|||||||
void
|
void
|
||||||
sc_hid_keyboard_init(struct sc_hid_keyboard *hid);
|
sc_hid_keyboard_init(struct sc_hid_keyboard *hid);
|
||||||
|
|
||||||
bool
|
void
|
||||||
sc_hid_keyboard_event_from_key(struct sc_hid_keyboard *hid,
|
sc_hid_keyboard_generate_open(struct sc_hid_open *hid_open);
|
||||||
struct sc_hid_event *hid_event,
|
|
||||||
const struct sc_key_event *event);
|
void
|
||||||
|
sc_hid_keyboard_generate_close(struct sc_hid_close *hid_close);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_hid_keyboard_event_from_mods(struct sc_hid_event *event,
|
sc_hid_keyboard_generate_input_from_key(struct sc_hid_keyboard *hid,
|
||||||
uint16_t mods_state);
|
struct sc_hid_input *hid_input,
|
||||||
|
const struct sc_key_event *event);
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_hid_keyboard_generate_input_from_mods(struct sc_hid_input *hid_input,
|
||||||
|
uint16_t mods_state);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -2,19 +2,19 @@
|
|||||||
|
|
||||||
// 1 byte for buttons + padding, 1 byte for X position, 1 byte for Y position,
|
// 1 byte for buttons + padding, 1 byte for X position, 1 byte for Y position,
|
||||||
// 1 byte for wheel motion
|
// 1 byte for wheel motion
|
||||||
#define HID_MOUSE_EVENT_SIZE 4
|
#define SC_HID_MOUSE_INPUT_SIZE 4
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mouse descriptor from the specification:
|
* Mouse descriptor from the specification:
|
||||||
* <https://www.usb.org/sites/default/files/hid1_11.pdf>
|
* <https://www.usb.org/document-library/device-class-definition-hid-111>
|
||||||
*
|
*
|
||||||
* Appendix E (p71): §E.10 Report Descriptor (Mouse)
|
* Appendix E (p71): §E.10 Report Descriptor (Mouse)
|
||||||
*
|
*
|
||||||
* The usage tags (like Wheel) are listed in "HID Usage Tables":
|
* The usage tags (like Wheel) are listed in "HID Usage Tables":
|
||||||
* <https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf>
|
* <https://www.usb.org/document-library/hid-usage-tables-15>
|
||||||
* §4 Generic Desktop Page (0x01) (p26)
|
* §4 Generic Desktop Page (0x01) (p32)
|
||||||
*/
|
*/
|
||||||
const uint8_t SC_HID_MOUSE_REPORT_DESC[] = {
|
static const uint8_t SC_HID_MOUSE_REPORT_DESC[] = {
|
||||||
// Usage Page (Generic Desktop)
|
// Usage Page (Generic Desktop)
|
||||||
0x05, 0x01,
|
0x05, 0x01,
|
||||||
// Usage (Mouse)
|
// Usage (Mouse)
|
||||||
@ -34,7 +34,7 @@ const uint8_t SC_HID_MOUSE_REPORT_DESC[] = {
|
|||||||
|
|
||||||
// Usage Minimum (1)
|
// Usage Minimum (1)
|
||||||
0x19, 0x01,
|
0x19, 0x01,
|
||||||
// Usage Maximum (5)
|
// Usage Maximum (5)
|
||||||
0x29, 0x05,
|
0x29, 0x05,
|
||||||
// Logical Minimum (0)
|
// Logical Minimum (0)
|
||||||
0x15, 0x00,
|
0x15, 0x00,
|
||||||
@ -62,9 +62,9 @@ const uint8_t SC_HID_MOUSE_REPORT_DESC[] = {
|
|||||||
0x09, 0x31,
|
0x09, 0x31,
|
||||||
// Usage (Wheel)
|
// Usage (Wheel)
|
||||||
0x09, 0x38,
|
0x09, 0x38,
|
||||||
// Local Minimum (-127)
|
// Logical Minimum (-127)
|
||||||
0x15, 0x81,
|
0x15, 0x81,
|
||||||
// Local Maximum (127)
|
// Logical Maximum (127)
|
||||||
0x25, 0x7F,
|
0x25, 0x7F,
|
||||||
// Report Size (8)
|
// Report Size (8)
|
||||||
0x75, 0x08,
|
0x75, 0x08,
|
||||||
@ -80,11 +80,8 @@ const uint8_t SC_HID_MOUSE_REPORT_DESC[] = {
|
|||||||
0xC0,
|
0xC0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const size_t SC_HID_MOUSE_REPORT_DESC_LEN =
|
|
||||||
sizeof(SC_HID_MOUSE_REPORT_DESC);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A mouse HID event is 4 bytes long:
|
* A mouse HID input report is 4 bytes long:
|
||||||
*
|
*
|
||||||
* - byte 0: buttons state
|
* - byte 0: buttons state
|
||||||
* - byte 1: relative x motion (signed byte from -127 to 127)
|
* - byte 1: relative x motion (signed byte from -127 to 127)
|
||||||
@ -125,10 +122,10 @@ const size_t SC_HID_MOUSE_REPORT_DESC_LEN =
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_hid_mouse_event_init(struct sc_hid_event *hid_event) {
|
sc_hid_mouse_input_init(struct sc_hid_input *hid_input) {
|
||||||
hid_event->size = HID_MOUSE_EVENT_SIZE;
|
hid_input->hid_id = SC_HID_ID_MOUSE;
|
||||||
// Leave hid_event->data uninitialized, it will be fully initialized by
|
hid_input->size = SC_HID_MOUSE_INPUT_SIZE;
|
||||||
// callers
|
// Leave ->data uninitialized, it will be fully initialized by callers
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t
|
static uint8_t
|
||||||
@ -153,11 +150,11 @@ sc_hid_buttons_from_buttons_state(uint8_t buttons_state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_hid_mouse_event_from_motion(struct sc_hid_event *hid_event,
|
sc_hid_mouse_generate_input_from_motion(struct sc_hid_input *hid_input,
|
||||||
const struct sc_mouse_motion_event *event) {
|
const struct sc_mouse_motion_event *event) {
|
||||||
sc_hid_mouse_event_init(hid_event);
|
sc_hid_mouse_input_init(hid_input);
|
||||||
|
|
||||||
uint8_t *data = hid_event->data;
|
uint8_t *data = hid_input->data;
|
||||||
data[0] = sc_hid_buttons_from_buttons_state(event->buttons_state);
|
data[0] = sc_hid_buttons_from_buttons_state(event->buttons_state);
|
||||||
data[1] = CLAMP(event->xrel, -127, 127);
|
data[1] = CLAMP(event->xrel, -127, 127);
|
||||||
data[2] = CLAMP(event->yrel, -127, 127);
|
data[2] = CLAMP(event->yrel, -127, 127);
|
||||||
@ -165,11 +162,11 @@ sc_hid_mouse_event_from_motion(struct sc_hid_event *hid_event,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_hid_mouse_event_from_click(struct sc_hid_event *hid_event,
|
sc_hid_mouse_generate_input_from_click(struct sc_hid_input *hid_input,
|
||||||
const struct sc_mouse_click_event *event) {
|
const struct sc_mouse_click_event *event) {
|
||||||
sc_hid_mouse_event_init(hid_event);
|
sc_hid_mouse_input_init(hid_input);
|
||||||
|
|
||||||
uint8_t *data = hid_event->data;
|
uint8_t *data = hid_input->data;
|
||||||
data[0] = sc_hid_buttons_from_buttons_state(event->buttons_state);
|
data[0] = sc_hid_buttons_from_buttons_state(event->buttons_state);
|
||||||
data[1] = 0; // no x motion
|
data[1] = 0; // no x motion
|
||||||
data[2] = 0; // no y motion
|
data[2] = 0; // no y motion
|
||||||
@ -177,11 +174,11 @@ sc_hid_mouse_event_from_click(struct sc_hid_event *hid_event,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_hid_mouse_event_from_scroll(struct sc_hid_event *hid_event,
|
sc_hid_mouse_generate_input_from_scroll(struct sc_hid_input *hid_input,
|
||||||
const struct sc_mouse_scroll_event *event) {
|
const struct sc_mouse_scroll_event *event) {
|
||||||
sc_hid_mouse_event_init(hid_event);
|
sc_hid_mouse_input_init(hid_input);
|
||||||
|
|
||||||
uint8_t *data = hid_event->data;
|
uint8_t *data = hid_input->data;
|
||||||
data[0] = 0; // buttons state irrelevant (and unknown)
|
data[0] = 0; // buttons state irrelevant (and unknown)
|
||||||
data[1] = 0; // no x motion
|
data[1] = 0; // no x motion
|
||||||
data[2] = 0; // no y motion
|
data[2] = 0; // no y motion
|
||||||
@ -190,3 +187,14 @@ sc_hid_mouse_event_from_scroll(struct sc_hid_event *hid_event,
|
|||||||
data[3] = CLAMP(event->vscroll, -127, 127);
|
data[3] = CLAMP(event->vscroll, -127, 127);
|
||||||
// Horizontal scrolling ignored
|
// Horizontal scrolling ignored
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sc_hid_mouse_generate_open(struct sc_hid_open *hid_open) {
|
||||||
|
hid_open->hid_id = SC_HID_ID_MOUSE;
|
||||||
|
hid_open->name = NULL; // No name specified after "scrcpy"
|
||||||
|
hid_open->report_desc = SC_HID_MOUSE_REPORT_DESC;
|
||||||
|
hid_open->report_desc_size = sizeof(SC_HID_MOUSE_REPORT_DESC);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sc_hid_mouse_generate_close(struct sc_hid_close *hid_close) {
|
||||||
|
hid_close->hid_id = SC_HID_ID_MOUSE;
|
||||||
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
#ifndef SC_HID_MOUSE_H
|
#ifndef SC_HID_MOUSE_H
|
||||||
#define SC_HID_MOUSE_H
|
#define SC_HID_MOUSE_H
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
@ -10,17 +8,24 @@
|
|||||||
#include "hid/hid_event.h"
|
#include "hid/hid_event.h"
|
||||||
#include "input_events.h"
|
#include "input_events.h"
|
||||||
|
|
||||||
extern const uint8_t SC_HID_MOUSE_REPORT_DESC[];
|
#define SC_HID_ID_MOUSE 2
|
||||||
extern const size_t SC_HID_MOUSE_REPORT_DESC_LEN;
|
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_hid_mouse_event_from_motion(struct sc_hid_event *hid_event,
|
sc_hid_mouse_generate_open(struct sc_hid_open *hid_open);
|
||||||
const struct sc_mouse_motion_event *event);
|
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_hid_mouse_event_from_click(struct sc_hid_event *hid_event,
|
sc_hid_mouse_generate_close(struct sc_hid_close *hid_close);
|
||||||
const struct sc_mouse_click_event *event);
|
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_hid_mouse_event_from_scroll(struct sc_hid_event *hid_event,
|
sc_hid_mouse_generate_input_from_motion(struct sc_hid_input *hid_input,
|
||||||
const struct sc_mouse_scroll_event *event);
|
const struct sc_mouse_motion_event *event);
|
||||||
|
|
||||||
|
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,
|
||||||
|
const struct sc_mouse_scroll_event *event);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
@ -117,21 +117,21 @@ decode_image(const char *path) {
|
|||||||
AVFrame *frame = av_frame_alloc();
|
AVFrame *frame = av_frame_alloc();
|
||||||
if (!frame) {
|
if (!frame) {
|
||||||
LOG_OOM();
|
LOG_OOM();
|
||||||
goto close_codec;
|
goto free_codec_ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
AVPacket *packet = av_packet_alloc();
|
AVPacket *packet = av_packet_alloc();
|
||||||
if (!packet) {
|
if (!packet) {
|
||||||
LOG_OOM();
|
LOG_OOM();
|
||||||
av_frame_free(&frame);
|
av_frame_free(&frame);
|
||||||
goto close_codec;
|
goto free_codec_ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (av_read_frame(ctx, packet) < 0) {
|
if (av_read_frame(ctx, packet) < 0) {
|
||||||
LOGE("Could not read frame");
|
LOGE("Could not read frame");
|
||||||
av_packet_free(&packet);
|
av_packet_free(&packet);
|
||||||
av_frame_free(&frame);
|
av_frame_free(&frame);
|
||||||
goto close_codec;
|
goto free_codec_ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ret;
|
int ret;
|
||||||
@ -139,22 +139,20 @@ decode_image(const char *path) {
|
|||||||
LOGE("Could not send icon packet: %d", ret);
|
LOGE("Could not send icon packet: %d", ret);
|
||||||
av_packet_free(&packet);
|
av_packet_free(&packet);
|
||||||
av_frame_free(&frame);
|
av_frame_free(&frame);
|
||||||
goto close_codec;
|
goto free_codec_ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((ret = avcodec_receive_frame(codec_ctx, frame)) != 0) {
|
if ((ret = avcodec_receive_frame(codec_ctx, frame)) != 0) {
|
||||||
LOGE("Could not receive icon frame: %d", ret);
|
LOGE("Could not receive icon frame: %d", ret);
|
||||||
av_packet_free(&packet);
|
av_packet_free(&packet);
|
||||||
av_frame_free(&frame);
|
av_frame_free(&frame);
|
||||||
goto close_codec;
|
goto free_codec_ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
av_packet_free(&packet);
|
av_packet_free(&packet);
|
||||||
|
|
||||||
result = frame;
|
result = frame;
|
||||||
|
|
||||||
close_codec:
|
|
||||||
avcodec_close(codec_ctx);
|
|
||||||
free_codec_ctx:
|
free_codec_ctx:
|
||||||
avcodec_free_context(&codec_ctx);
|
avcodec_free_context(&codec_ctx);
|
||||||
close_input:
|
close_input:
|
||||||
|
@ -323,6 +323,38 @@ enum sc_mouse_button {
|
|||||||
SC_MOUSE_BUTTON_X2 = SDL_BUTTON(SDL_BUTTON_X2),
|
SC_MOUSE_BUTTON_X2 = SDL_BUTTON(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,
|
||||||
|
};
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
static_assert(sizeof(enum sc_mod) >= sizeof(SDL_Keymod),
|
static_assert(sizeof(enum sc_mod) >= sizeof(SDL_Keymod),
|
||||||
"SDL_Keymod must be convertible to sc_mod");
|
"SDL_Keymod must be convertible to sc_mod");
|
||||||
|
|
||||||
@ -380,6 +412,33 @@ struct sc_touch_event {
|
|||||||
float pressure;
|
float pressure;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum sc_gamepad_device_event_type {
|
||||||
|
SC_GAMEPAD_DEVICE_ADDED,
|
||||||
|
SC_GAMEPAD_DEVICE_REMOVED,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
struct sc_gamepad_device_event {
|
||||||
|
enum sc_gamepad_device_event_type type;
|
||||||
|
uint32_t gamepad_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sc_gamepad_button_event {
|
||||||
|
uint32_t gamepad_id;
|
||||||
|
enum sc_action action;
|
||||||
|
enum sc_gamepad_button button;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sc_gamepad_axis_event {
|
||||||
|
uint32_t gamepad_id;
|
||||||
|
enum sc_gamepad_axis axis;
|
||||||
|
int16_t value;
|
||||||
|
};
|
||||||
|
|
||||||
static inline uint16_t
|
static inline uint16_t
|
||||||
sc_mods_state_from_sdl(uint16_t mods_state) {
|
sc_mods_state_from_sdl(uint16_t mods_state) {
|
||||||
return mods_state;
|
return mods_state;
|
||||||
@ -437,25 +496,50 @@ sc_mouse_button_from_sdl(uint8_t button) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static inline uint8_t
|
static inline uint8_t
|
||||||
sc_mouse_buttons_state_from_sdl(uint32_t buttons_state,
|
sc_mouse_buttons_state_from_sdl(uint32_t buttons_state) {
|
||||||
const struct sc_mouse_bindings *mb) {
|
|
||||||
assert(buttons_state < 0x100); // fits in uint8_t
|
assert(buttons_state < 0x100); // fits in uint8_t
|
||||||
|
|
||||||
uint8_t mask = SC_MOUSE_BUTTON_LEFT;
|
// SC_MOUSE_BUTTON_* constants are initialized from SDL_BUTTON(index)
|
||||||
if (!mb || mb->right_click == SC_MOUSE_BINDING_CLICK) {
|
return buttons_state;
|
||||||
mask |= SC_MOUSE_BUTTON_RIGHT;
|
}
|
||||||
}
|
|
||||||
if (!mb || mb->middle_click == SC_MOUSE_BINDING_CLICK) {
|
|
||||||
mask |= SC_MOUSE_BUTTON_MIDDLE;
|
|
||||||
}
|
|
||||||
if (!mb || mb->click4 == SC_MOUSE_BINDING_CLICK) {
|
|
||||||
mask |= SC_MOUSE_BUTTON_X1;
|
|
||||||
}
|
|
||||||
if (!mb || mb->click5 == SC_MOUSE_BINDING_CLICK) {
|
|
||||||
mask |= SC_MOUSE_BUTTON_X2;
|
|
||||||
}
|
|
||||||
|
|
||||||
return buttons_state & mask;
|
static inline enum sc_gamepad_device_event_type
|
||||||
|
sc_gamepad_device_event_type_from_sdl_type(uint32_t type) {
|
||||||
|
assert(type == SDL_CONTROLLERDEVICEADDED
|
||||||
|
|| type == SDL_CONTROLLERDEVICEREMOVED);
|
||||||
|
if (type == SDL_CONTROLLERDEVICEADDED) {
|
||||||
|
return SC_GAMEPAD_DEVICE_ADDED;
|
||||||
|
}
|
||||||
|
return SC_GAMEPAD_DEVICE_REMOVED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline enum sc_gamepad_axis
|
||||||
|
sc_gamepad_axis_from_sdl(uint8_t axis) {
|
||||||
|
if (axis <= SDL_CONTROLLER_AXIS_TRIGGERRIGHT) {
|
||||||
|
// SC_GAMEPAD_AXIS_* constants are initialized from
|
||||||
|
// SDL_CONTROLLER_AXIS_*
|
||||||
|
return axis;
|
||||||
|
}
|
||||||
|
return SC_GAMEPAD_AXIS_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline enum sc_gamepad_button
|
||||||
|
sc_gamepad_button_from_sdl(uint8_t button) {
|
||||||
|
if (button <= SDL_CONTROLLER_BUTTON_DPAD_RIGHT) {
|
||||||
|
// SC_GAMEPAD_BUTTON_* constants are initialized from
|
||||||
|
// SDL_CONTROLLER_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) {
|
||||||
|
return SC_ACTION_DOWN;
|
||||||
|
}
|
||||||
|
return SC_ACTION_UP;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -52,32 +52,24 @@ is_shortcut_key(struct sc_input_manager *im, SDL_Keycode keycode) {
|
|||||||
|| (im->sdl_shortcut_mods & KMOD_RGUI && keycode == SDLK_RGUI);
|
|| (im->sdl_shortcut_mods & KMOD_RGUI && keycode == SDLK_RGUI);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
|
||||||
mouse_bindings_has_secondary_click(const struct sc_mouse_bindings *mb) {
|
|
||||||
return mb->right_click == SC_MOUSE_BINDING_CLICK
|
|
||||||
|| mb->middle_click == SC_MOUSE_BINDING_CLICK
|
|
||||||
|| mb->click4 == SC_MOUSE_BINDING_CLICK
|
|
||||||
|| mb->click5 == SC_MOUSE_BINDING_CLICK;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_input_manager_init(struct sc_input_manager *im,
|
sc_input_manager_init(struct sc_input_manager *im,
|
||||||
const struct sc_input_manager_params *params) {
|
const struct sc_input_manager_params *params) {
|
||||||
// A key/mouse processor may not be present if there is no controller
|
// A key/mouse processor may not be present if there is no controller
|
||||||
assert((!params->kp && !params->mp) || params->controller);
|
assert((!params->kp && !params->mp && !params->gp) || params->controller);
|
||||||
// A processor must have ops initialized
|
// A processor must have ops initialized
|
||||||
assert(!params->kp || params->kp->ops);
|
assert(!params->kp || params->kp->ops);
|
||||||
assert(!params->mp || params->mp->ops);
|
assert(!params->mp || params->mp->ops);
|
||||||
|
assert(!params->gp || params->gp->ops);
|
||||||
|
|
||||||
im->controller = params->controller;
|
im->controller = params->controller;
|
||||||
im->fp = params->fp;
|
im->fp = params->fp;
|
||||||
im->screen = params->screen;
|
im->screen = params->screen;
|
||||||
im->kp = params->kp;
|
im->kp = params->kp;
|
||||||
im->mp = params->mp;
|
im->mp = params->mp;
|
||||||
|
im->gp = params->gp;
|
||||||
|
|
||||||
im->mouse_bindings = params->mouse_bindings;
|
im->mouse_bindings = params->mouse_bindings;
|
||||||
im->has_secondary_click =
|
|
||||||
mouse_bindings_has_secondary_click(&im->mouse_bindings);
|
|
||||||
im->legacy_paste = params->legacy_paste;
|
im->legacy_paste = params->legacy_paste;
|
||||||
im->clipboard_autosync = params->clipboard_autosync;
|
im->clipboard_autosync = params->clipboard_autosync;
|
||||||
|
|
||||||
@ -87,6 +79,8 @@ sc_input_manager_init(struct sc_input_manager *im,
|
|||||||
im->vfinger_invert_x = false;
|
im->vfinger_invert_x = false;
|
||||||
im->vfinger_invert_y = false;
|
im->vfinger_invert_y = false;
|
||||||
|
|
||||||
|
im->mouse_buttons_state = 0;
|
||||||
|
|
||||||
im->last_keycode = SDLK_UNKNOWN;
|
im->last_keycode = SDLK_UNKNOWN;
|
||||||
im->last_mod = 0;
|
im->last_mod = 0;
|
||||||
im->key_repeat = 0;
|
im->key_repeat = 0;
|
||||||
@ -375,9 +369,7 @@ simulate_virtual_finger(struct sc_input_manager *im,
|
|||||||
msg.inject_touch_event.action = action;
|
msg.inject_touch_event.action = action;
|
||||||
msg.inject_touch_event.position.screen_size = im->screen->frame_size;
|
msg.inject_touch_event.position.screen_size = im->screen->frame_size;
|
||||||
msg.inject_touch_event.position.point = point;
|
msg.inject_touch_event.position.point = point;
|
||||||
msg.inject_touch_event.pointer_id =
|
msg.inject_touch_event.pointer_id = SC_POINTER_ID_VIRTUAL_FINGER;
|
||||||
im->has_secondary_click ? POINTER_ID_VIRTUAL_MOUSE
|
|
||||||
: POINTER_ID_VIRTUAL_FINGER;
|
|
||||||
msg.inject_touch_event.pressure = up ? 0.0f : 1.0f;
|
msg.inject_touch_event.pressure = up ? 0.0f : 1.0f;
|
||||||
msg.inject_touch_event.action_button = 0;
|
msg.inject_touch_event.action_button = 0;
|
||||||
msg.inject_touch_event.buttons = 0;
|
msg.inject_touch_event.buttons = 0;
|
||||||
@ -410,7 +402,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
|||||||
bool paused = im->screen->paused;
|
bool paused = im->screen->paused;
|
||||||
bool video = im->screen->video;
|
bool video = im->screen->video;
|
||||||
|
|
||||||
SDL_Keycode keycode = event->keysym.sym;
|
SDL_Keycode sdl_keycode = event->keysym.sym;
|
||||||
uint16_t mod = event->keysym.mod;
|
uint16_t mod = event->keysym.mod;
|
||||||
bool down = event->type == SDL_KEYDOWN;
|
bool down = event->type == SDL_KEYDOWN;
|
||||||
bool ctrl = event->keysym.mod & KMOD_CTRL;
|
bool ctrl = event->keysym.mod & KMOD_CTRL;
|
||||||
@ -422,21 +414,21 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
|||||||
// The second condition is necessary to ignore the release of the modifier
|
// The second condition is necessary to ignore the release of the modifier
|
||||||
// key (because in this case mod is 0).
|
// key (because in this case mod is 0).
|
||||||
bool is_shortcut = is_shortcut_mod(im, mod)
|
bool is_shortcut = is_shortcut_mod(im, mod)
|
||||||
|| is_shortcut_key(im, keycode);
|
|| is_shortcut_key(im, sdl_keycode);
|
||||||
|
|
||||||
if (down && !repeat) {
|
if (down && !repeat) {
|
||||||
if (keycode == im->last_keycode && mod == im->last_mod) {
|
if (sdl_keycode == im->last_keycode && mod == im->last_mod) {
|
||||||
++im->key_repeat;
|
++im->key_repeat;
|
||||||
} else {
|
} else {
|
||||||
im->key_repeat = 0;
|
im->key_repeat = 0;
|
||||||
im->last_keycode = keycode;
|
im->last_keycode = sdl_keycode;
|
||||||
im->last_mod = mod;
|
im->last_mod = mod;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_shortcut) {
|
if (is_shortcut) {
|
||||||
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
|
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
|
||||||
switch (keycode) {
|
switch (sdl_keycode) {
|
||||||
case SDLK_h:
|
case SDLK_h:
|
||||||
if (im->kp && !shift && !repeat && !paused) {
|
if (im->kp && !shift && !repeat && !paused) {
|
||||||
action_home(im, action);
|
action_home(im, action);
|
||||||
@ -595,7 +587,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint64_t ack_to_wait = SC_SEQUENCE_INVALID;
|
uint64_t ack_to_wait = SC_SEQUENCE_INVALID;
|
||||||
bool is_ctrl_v = ctrl && !shift && 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->clipboard_autosync && is_ctrl_v) {
|
||||||
if (im->legacy_paste) {
|
if (im->legacy_paste) {
|
||||||
// inject the text as input events
|
// inject the text as input events
|
||||||
@ -623,10 +615,20 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum sc_keycode keycode = sc_keycode_from_sdl(sdl_keycode);
|
||||||
|
if (keycode == SC_KEYCODE_UNKNOWN) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum sc_scancode scancode = sc_scancode_from_sdl(event->keysym.scancode);
|
||||||
|
if (scancode == SC_SCANCODE_UNKNOWN) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
struct sc_key_event evt = {
|
struct sc_key_event evt = {
|
||||||
.action = sc_action_from_sdl_keyboard_type(event->type),
|
.action = sc_action_from_sdl_keyboard_type(event->type),
|
||||||
.keycode = sc_keycode_from_sdl(event->keysym.sym),
|
.keycode = keycode,
|
||||||
.scancode = sc_scancode_from_sdl(event->keysym.scancode),
|
.scancode = scancode,
|
||||||
.repeat = event->repeat,
|
.repeat = event->repeat,
|
||||||
.mods_state = sc_mods_state_from_sdl(event->keysym.mod),
|
.mods_state = sc_mods_state_from_sdl(event->keysym.mod),
|
||||||
};
|
};
|
||||||
@ -662,12 +664,11 @@ sc_input_manager_process_mouse_motion(struct sc_input_manager *im,
|
|||||||
|
|
||||||
struct sc_mouse_motion_event evt = {
|
struct sc_mouse_motion_event evt = {
|
||||||
.position = sc_input_manager_get_position(im, event->x, event->y),
|
.position = sc_input_manager_get_position(im, event->x, event->y),
|
||||||
.pointer_id = im->has_secondary_click ? POINTER_ID_MOUSE
|
.pointer_id = im->vfinger_down ? SC_POINTER_ID_GENERIC_FINGER
|
||||||
: POINTER_ID_GENERIC_FINGER,
|
: SC_POINTER_ID_MOUSE,
|
||||||
.xrel = event->xrel,
|
.xrel = event->xrel,
|
||||||
.yrel = event->yrel,
|
.yrel = event->yrel,
|
||||||
.buttons_state =
|
.buttons_state = im->mouse_buttons_state,
|
||||||
sc_mouse_buttons_state_from_sdl(event->state, &im->mouse_bindings),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
assert(im->mp->ops->process_mouse_motion);
|
assert(im->mp->ops->process_mouse_motion);
|
||||||
@ -719,7 +720,7 @@ sc_input_manager_process_touch(struct sc_input_manager *im,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static enum sc_mouse_binding
|
static enum sc_mouse_binding
|
||||||
sc_input_manager_get_binding(const struct sc_mouse_bindings *bindings,
|
sc_input_manager_get_binding(const struct sc_mouse_binding_set *bindings,
|
||||||
uint8_t sdl_button) {
|
uint8_t sdl_button) {
|
||||||
switch (sdl_button) {
|
switch (sdl_button) {
|
||||||
case SDL_BUTTON_LEFT:
|
case SDL_BUTTON_LEFT:
|
||||||
@ -748,11 +749,29 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
|||||||
bool control = im->controller;
|
bool control = im->controller;
|
||||||
bool paused = im->screen->paused;
|
bool paused = im->screen->paused;
|
||||||
bool down = event->type == SDL_MOUSEBUTTONDOWN;
|
bool down = event->type == SDL_MOUSEBUTTONDOWN;
|
||||||
|
|
||||||
|
enum sc_mouse_button button = sc_mouse_button_from_sdl(event->button);
|
||||||
|
if (button == SC_MOUSE_BUTTON_UNKNOWN) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!down) {
|
||||||
|
// Mark the button as released
|
||||||
|
im->mouse_buttons_state &= ~button;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Keymod keymod = SDL_GetModState();
|
||||||
|
bool ctrl_pressed = keymod & KMOD_CTRL;
|
||||||
|
bool shift_pressed = keymod & KMOD_SHIFT;
|
||||||
|
|
||||||
if (control && !paused) {
|
if (control && !paused) {
|
||||||
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
|
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
|
||||||
|
|
||||||
|
struct sc_mouse_binding_set *bindings = !shift_pressed
|
||||||
|
? &im->mouse_bindings.pri
|
||||||
|
: &im->mouse_bindings.sec;
|
||||||
enum sc_mouse_binding binding =
|
enum sc_mouse_binding binding =
|
||||||
sc_input_manager_get_binding(&im->mouse_bindings, event->button);
|
sc_input_manager_get_binding(bindings, event->button);
|
||||||
assert(binding != SC_MOUSE_BINDING_AUTO);
|
assert(binding != SC_MOUSE_BINDING_AUTO);
|
||||||
switch (binding) {
|
switch (binding) {
|
||||||
case SC_MOUSE_BINDING_DISABLED:
|
case SC_MOUSE_BINDING_DISABLED:
|
||||||
@ -811,16 +830,23 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t sdl_buttons_state = SDL_GetMouseState(NULL, NULL);
|
if (down) {
|
||||||
|
// Mark the button as pressed
|
||||||
|
im->mouse_buttons_state |= button;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool change_vfinger = event->button == SDL_BUTTON_LEFT &&
|
||||||
|
((down && !im->vfinger_down && (ctrl_pressed ^ shift_pressed)) ||
|
||||||
|
(!down && im->vfinger_down));
|
||||||
|
bool use_finger = im->vfinger_down || change_vfinger;
|
||||||
|
|
||||||
struct sc_mouse_click_event evt = {
|
struct sc_mouse_click_event evt = {
|
||||||
.position = sc_input_manager_get_position(im, event->x, event->y),
|
.position = sc_input_manager_get_position(im, event->x, event->y),
|
||||||
.action = sc_action_from_sdl_mousebutton_type(event->type),
|
.action = sc_action_from_sdl_mousebutton_type(event->type),
|
||||||
.button = sc_mouse_button_from_sdl(event->button),
|
.button = button,
|
||||||
.pointer_id = im->has_secondary_click ? POINTER_ID_MOUSE
|
.pointer_id = use_finger ? SC_POINTER_ID_GENERIC_FINGER
|
||||||
: POINTER_ID_GENERIC_FINGER,
|
: SC_POINTER_ID_MOUSE,
|
||||||
.buttons_state = sc_mouse_buttons_state_from_sdl(sdl_buttons_state,
|
.buttons_state = im->mouse_buttons_state,
|
||||||
&im->mouse_bindings),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
assert(im->mp->ops->process_mouse_click);
|
assert(im->mp->ops->process_mouse_click);
|
||||||
@ -846,14 +872,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
|||||||
// can be used instead of Ctrl. The "virtual finger" has a position
|
// can be used instead of Ctrl. The "virtual finger" has a position
|
||||||
// inverted with respect to the vertical axis of symmetry in the middle of
|
// inverted with respect to the vertical axis of symmetry in the middle of
|
||||||
// the screen.
|
// the screen.
|
||||||
const SDL_Keymod keymod = SDL_GetModState();
|
if (change_vfinger) {
|
||||||
const bool ctrl_pressed = keymod & KMOD_CTRL;
|
|
||||||
const bool shift_pressed = keymod & KMOD_SHIFT;
|
|
||||||
if (event->button == SDL_BUTTON_LEFT &&
|
|
||||||
((down && !im->vfinger_down &&
|
|
||||||
((ctrl_pressed && !shift_pressed) ||
|
|
||||||
(!ctrl_pressed && shift_pressed))) ||
|
|
||||||
(!down && im->vfinger_down))) {
|
|
||||||
struct sc_point mouse =
|
struct sc_point mouse =
|
||||||
sc_screen_convert_window_to_frame_coords(im->screen, event->x,
|
sc_screen_convert_window_to_frame_coords(im->screen, event->x,
|
||||||
event->y);
|
event->y);
|
||||||
@ -886,6 +905,7 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
|
|||||||
int mouse_x;
|
int mouse_x;
|
||||||
int mouse_y;
|
int mouse_y;
|
||||||
uint32_t buttons = SDL_GetMouseState(&mouse_x, &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 = {
|
struct sc_mouse_scroll_event evt = {
|
||||||
.position = sc_input_manager_get_position(im, mouse_x, mouse_y),
|
.position = sc_input_manager_get_position(im, mouse_x, mouse_y),
|
||||||
@ -896,13 +916,84 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
|
|||||||
.hscroll = CLAMP(event->x, -1, 1),
|
.hscroll = CLAMP(event->x, -1, 1),
|
||||||
.vscroll = CLAMP(event->y, -1, 1),
|
.vscroll = CLAMP(event->y, -1, 1),
|
||||||
#endif
|
#endif
|
||||||
.buttons_state = sc_mouse_buttons_state_from_sdl(buttons,
|
.buttons_state = im->mouse_buttons_state,
|
||||||
&im->mouse_bindings),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
im->mp->ops->process_mouse_scroll(im->mp, &evt);
|
im->mp->ops->process_mouse_scroll(im->mp, &evt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_input_manager_process_gamepad_device(struct sc_input_manager *im,
|
||||||
|
const SDL_ControllerDeviceEvent *event) {
|
||||||
|
SDL_JoystickID id;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
id = SDL_JoystickInstanceID(joystick);
|
||||||
|
} else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
|
||||||
|
id = event->which;
|
||||||
|
|
||||||
|
SDL_GameController *gc = SDL_GameControllerFromInstanceID(id);
|
||||||
|
if (gc) {
|
||||||
|
SDL_GameControllerClose(gc);
|
||||||
|
} else {
|
||||||
|
LOGW("Unknown gamepad device removed");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Nothing to do
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sc_gamepad_device_event evt = {
|
||||||
|
.type = sc_gamepad_device_event_type_from_sdl_type(event->type),
|
||||||
|
.gamepad_id = id,
|
||||||
|
};
|
||||||
|
im->gp->ops->process_gamepad_device(im->gp, &evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_input_manager_process_gamepad_axis(struct sc_input_manager *im,
|
||||||
|
const SDL_ControllerAxisEvent *event) {
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
im->gp->ops->process_gamepad_axis(im->gp, &evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_input_manager_process_gamepad_button(struct sc_input_manager *im,
|
||||||
|
const SDL_ControllerButtonEvent *event) {
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
im->gp->ops->process_gamepad_button(im->gp, &evt);
|
||||||
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
is_apk(const char *file) {
|
is_apk(const char *file) {
|
||||||
const char *ext = strrchr(file, '.');
|
const char *ext = strrchr(file, '.');
|
||||||
@ -975,6 +1066,27 @@ sc_input_manager_handle_event(struct sc_input_manager *im,
|
|||||||
}
|
}
|
||||||
sc_input_manager_process_touch(im, &event->tfinger);
|
sc_input_manager_process_touch(im, &event->tfinger);
|
||||||
break;
|
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);
|
||||||
|
break;
|
||||||
|
case SDL_CONTROLLERAXISMOTION:
|
||||||
|
if (!im->gp || paused) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sc_input_manager_process_gamepad_axis(im, &event->caxis);
|
||||||
|
break;
|
||||||
|
case SDL_CONTROLLERBUTTONDOWN:
|
||||||
|
case SDL_CONTROLLERBUTTONUP:
|
||||||
|
if (!im->gp || paused) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sc_input_manager_process_gamepad_button(im, &event->cbutton);
|
||||||
|
break;
|
||||||
case SDL_DROPFILE: {
|
case SDL_DROPFILE: {
|
||||||
if (!control) {
|
if (!control) {
|
||||||
break;
|
break;
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
#include "file_pusher.h"
|
#include "file_pusher.h"
|
||||||
#include "fps_counter.h"
|
#include "fps_counter.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
|
#include "trait/gamepad_processor.h"
|
||||||
#include "trait/key_processor.h"
|
#include "trait/key_processor.h"
|
||||||
#include "trait/mouse_processor.h"
|
#include "trait/mouse_processor.h"
|
||||||
|
|
||||||
@ -21,9 +22,9 @@ struct sc_input_manager {
|
|||||||
|
|
||||||
struct sc_key_processor *kp;
|
struct sc_key_processor *kp;
|
||||||
struct sc_mouse_processor *mp;
|
struct sc_mouse_processor *mp;
|
||||||
|
struct sc_gamepad_processor *gp;
|
||||||
|
|
||||||
struct sc_mouse_bindings mouse_bindings;
|
struct sc_mouse_bindings mouse_bindings;
|
||||||
bool has_secondary_click;
|
|
||||||
bool legacy_paste;
|
bool legacy_paste;
|
||||||
bool clipboard_autosync;
|
bool clipboard_autosync;
|
||||||
|
|
||||||
@ -33,6 +34,8 @@ struct sc_input_manager {
|
|||||||
bool vfinger_invert_x;
|
bool vfinger_invert_x;
|
||||||
bool vfinger_invert_y;
|
bool vfinger_invert_y;
|
||||||
|
|
||||||
|
uint8_t mouse_buttons_state; // OR of enum sc_mouse_button values
|
||||||
|
|
||||||
// Tracks the number of identical consecutive shortcut key down events.
|
// Tracks the number of identical consecutive shortcut key down events.
|
||||||
// Not to be confused with event->repeat, which counts the number of
|
// Not to be confused with event->repeat, which counts the number of
|
||||||
// system-generated repeated key presses.
|
// system-generated repeated key presses.
|
||||||
@ -49,6 +52,7 @@ struct sc_input_manager_params {
|
|||||||
struct sc_screen *screen;
|
struct sc_screen *screen;
|
||||||
struct sc_key_processor *kp;
|
struct sc_key_processor *kp;
|
||||||
struct sc_mouse_processor *mp;
|
struct sc_mouse_processor *mp;
|
||||||
|
struct sc_gamepad_processor *gp;
|
||||||
|
|
||||||
struct sc_mouse_bindings mouse_bindings;
|
struct sc_mouse_bindings mouse_bindings;
|
||||||
bool legacy_paste;
|
bool legacy_paste;
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
#include "usb/scrcpy_otg.h"
|
#include "usb/scrcpy_otg.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/net.h"
|
#include "util/net.h"
|
||||||
|
#include "util/thread.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@ -67,6 +68,9 @@ main_scrcpy(int argc, char *argv[]) {
|
|||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The current thread is the main thread
|
||||||
|
SC_MAIN_THREAD_ID = sc_thread_get_id();
|
||||||
|
|
||||||
#ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL
|
#ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL
|
||||||
av_register_all();
|
av_register_all();
|
||||||
#endif
|
#endif
|
||||||
|
@ -58,17 +58,18 @@ convert_touch_action(enum sc_touch_action action) {
|
|||||||
static void
|
static void
|
||||||
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
|
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
|
||||||
const struct sc_mouse_motion_event *event) {
|
const struct sc_mouse_motion_event *event) {
|
||||||
if (!event->buttons_state) {
|
struct sc_mouse_sdk *m = DOWNCAST(mp);
|
||||||
|
|
||||||
|
if (!m->mouse_hover && !event->buttons_state) {
|
||||||
// Do not send motion events when no click is pressed
|
// Do not send motion events when no click is pressed
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_mouse_sdk *m = DOWNCAST(mp);
|
|
||||||
|
|
||||||
struct sc_control_msg msg = {
|
struct sc_control_msg msg = {
|
||||||
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
|
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
|
||||||
.inject_touch_event = {
|
.inject_touch_event = {
|
||||||
.action = AMOTION_EVENT_ACTION_MOVE,
|
.action = event->buttons_state ? AMOTION_EVENT_ACTION_MOVE
|
||||||
|
: AMOTION_EVENT_ACTION_HOVER_MOVE,
|
||||||
.pointer_id = event->pointer_id,
|
.pointer_id = event->pointer_id,
|
||||||
.position = event->position,
|
.position = event->position,
|
||||||
.pressure = 1.f,
|
.pressure = 1.f,
|
||||||
@ -145,8 +146,10 @@ sc_mouse_processor_process_touch(struct sc_mouse_processor *mp,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller) {
|
sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller,
|
||||||
|
bool mouse_hover) {
|
||||||
m->controller = controller;
|
m->controller = controller;
|
||||||
|
m->mouse_hover = mouse_hover;
|
||||||
|
|
||||||
static const struct sc_mouse_processor_ops ops = {
|
static const struct sc_mouse_processor_ops ops = {
|
||||||
.process_mouse_motion = sc_mouse_processor_process_mouse_motion,
|
.process_mouse_motion = sc_mouse_processor_process_mouse_motion,
|
||||||
|
@ -13,9 +13,11 @@ struct sc_mouse_sdk {
|
|||||||
struct sc_mouse_processor mouse_processor; // mouse processor trait
|
struct sc_mouse_processor mouse_processor; // mouse processor trait
|
||||||
|
|
||||||
struct sc_controller *controller;
|
struct sc_controller *controller;
|
||||||
|
bool mouse_hover;
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller);
|
sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller,
|
||||||
|
bool mouse_hover);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -23,11 +23,20 @@ const struct scrcpy_options scrcpy_options_default = {
|
|||||||
.record_format = SC_RECORD_FORMAT_AUTO,
|
.record_format = SC_RECORD_FORMAT_AUTO,
|
||||||
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_AUTO,
|
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_AUTO,
|
||||||
.mouse_input_mode = SC_MOUSE_INPUT_MODE_AUTO,
|
.mouse_input_mode = SC_MOUSE_INPUT_MODE_AUTO,
|
||||||
|
.gamepad_input_mode = SC_GAMEPAD_INPUT_MODE_DISABLED,
|
||||||
.mouse_bindings = {
|
.mouse_bindings = {
|
||||||
.right_click = SC_MOUSE_BINDING_AUTO,
|
.pri = {
|
||||||
.middle_click = SC_MOUSE_BINDING_AUTO,
|
.right_click = SC_MOUSE_BINDING_AUTO,
|
||||||
.click4 = SC_MOUSE_BINDING_AUTO,
|
.middle_click = SC_MOUSE_BINDING_AUTO,
|
||||||
.click5 = SC_MOUSE_BINDING_AUTO,
|
.click4 = SC_MOUSE_BINDING_AUTO,
|
||||||
|
.click5 = SC_MOUSE_BINDING_AUTO,
|
||||||
|
},
|
||||||
|
.sec = {
|
||||||
|
.right_click = SC_MOUSE_BINDING_AUTO,
|
||||||
|
.middle_click = SC_MOUSE_BINDING_AUTO,
|
||||||
|
.click4 = SC_MOUSE_BINDING_AUTO,
|
||||||
|
.click5 = SC_MOUSE_BINDING_AUTO,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
.camera_facing = SC_CAMERA_FACING_ANY,
|
.camera_facing = SC_CAMERA_FACING_ANY,
|
||||||
.port_range = {
|
.port_range = {
|
||||||
@ -40,7 +49,7 @@ const struct scrcpy_options scrcpy_options_default = {
|
|||||||
.max_size = 0,
|
.max_size = 0,
|
||||||
.video_bit_rate = 0,
|
.video_bit_rate = 0,
|
||||||
.audio_bit_rate = 0,
|
.audio_bit_rate = 0,
|
||||||
.max_fps = 0,
|
.max_fps = NULL,
|
||||||
.lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED,
|
.lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED,
|
||||||
.display_orientation = SC_ORIENTATION_0,
|
.display_orientation = SC_ORIENTATION_0,
|
||||||
.record_orientation = SC_ORIENTATION_0,
|
.record_orientation = SC_ORIENTATION_0,
|
||||||
@ -92,6 +101,8 @@ const struct scrcpy_options scrcpy_options_default = {
|
|||||||
.camera_high_speed = false,
|
.camera_high_speed = false,
|
||||||
.list = 0,
|
.list = 0,
|
||||||
.window = true,
|
.window = true,
|
||||||
|
.mouse_hover = true,
|
||||||
|
.audio_dup = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum sc_orientation
|
enum sc_orientation
|
||||||
|
@ -59,6 +59,7 @@ enum sc_audio_source {
|
|||||||
SC_AUDIO_SOURCE_AUTO, // OUTPUT for video DISPLAY, MIC for video CAMERA
|
SC_AUDIO_SOURCE_AUTO, // OUTPUT for video DISPLAY, MIC for video CAMERA
|
||||||
SC_AUDIO_SOURCE_OUTPUT,
|
SC_AUDIO_SOURCE_OUTPUT,
|
||||||
SC_AUDIO_SOURCE_MIC,
|
SC_AUDIO_SOURCE_MIC,
|
||||||
|
SC_AUDIO_SOURCE_PLAYBACK,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum sc_camera_facing {
|
enum sc_camera_facing {
|
||||||
@ -141,6 +142,7 @@ enum sc_lock_video_orientation {
|
|||||||
|
|
||||||
enum sc_keyboard_input_mode {
|
enum sc_keyboard_input_mode {
|
||||||
SC_KEYBOARD_INPUT_MODE_AUTO,
|
SC_KEYBOARD_INPUT_MODE_AUTO,
|
||||||
|
SC_KEYBOARD_INPUT_MODE_UHID_OR_AOA, // normal vs otg mode
|
||||||
SC_KEYBOARD_INPUT_MODE_DISABLED,
|
SC_KEYBOARD_INPUT_MODE_DISABLED,
|
||||||
SC_KEYBOARD_INPUT_MODE_SDK,
|
SC_KEYBOARD_INPUT_MODE_SDK,
|
||||||
SC_KEYBOARD_INPUT_MODE_UHID,
|
SC_KEYBOARD_INPUT_MODE_UHID,
|
||||||
@ -149,12 +151,20 @@ enum sc_keyboard_input_mode {
|
|||||||
|
|
||||||
enum sc_mouse_input_mode {
|
enum sc_mouse_input_mode {
|
||||||
SC_MOUSE_INPUT_MODE_AUTO,
|
SC_MOUSE_INPUT_MODE_AUTO,
|
||||||
|
SC_MOUSE_INPUT_MODE_UHID_OR_AOA, // normal vs otg mode
|
||||||
SC_MOUSE_INPUT_MODE_DISABLED,
|
SC_MOUSE_INPUT_MODE_DISABLED,
|
||||||
SC_MOUSE_INPUT_MODE_SDK,
|
SC_MOUSE_INPUT_MODE_SDK,
|
||||||
SC_MOUSE_INPUT_MODE_UHID,
|
SC_MOUSE_INPUT_MODE_UHID,
|
||||||
SC_MOUSE_INPUT_MODE_AOA,
|
SC_MOUSE_INPUT_MODE_AOA,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum sc_gamepad_input_mode {
|
||||||
|
SC_GAMEPAD_INPUT_MODE_DISABLED,
|
||||||
|
SC_GAMEPAD_INPUT_MODE_UHID_OR_AOA, // normal vs otg mode
|
||||||
|
SC_GAMEPAD_INPUT_MODE_UHID,
|
||||||
|
SC_GAMEPAD_INPUT_MODE_AOA,
|
||||||
|
};
|
||||||
|
|
||||||
enum sc_mouse_binding {
|
enum sc_mouse_binding {
|
||||||
SC_MOUSE_BINDING_AUTO,
|
SC_MOUSE_BINDING_AUTO,
|
||||||
SC_MOUSE_BINDING_DISABLED,
|
SC_MOUSE_BINDING_DISABLED,
|
||||||
@ -165,13 +175,18 @@ enum sc_mouse_binding {
|
|||||||
SC_MOUSE_BINDING_EXPAND_NOTIFICATION_PANEL,
|
SC_MOUSE_BINDING_EXPAND_NOTIFICATION_PANEL,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_mouse_bindings {
|
struct sc_mouse_binding_set {
|
||||||
enum sc_mouse_binding right_click;
|
enum sc_mouse_binding right_click;
|
||||||
enum sc_mouse_binding middle_click;
|
enum sc_mouse_binding middle_click;
|
||||||
enum sc_mouse_binding click4;
|
enum sc_mouse_binding click4;
|
||||||
enum sc_mouse_binding click5;
|
enum sc_mouse_binding click5;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct sc_mouse_bindings {
|
||||||
|
struct sc_mouse_binding_set pri;
|
||||||
|
struct sc_mouse_binding_set sec; // When Shift is pressed
|
||||||
|
};
|
||||||
|
|
||||||
enum sc_key_inject_mode {
|
enum sc_key_inject_mode {
|
||||||
// Inject special keys, letters and space as key events.
|
// Inject special keys, letters and space as key events.
|
||||||
// Inject numbers and punctuation as text events.
|
// Inject numbers and punctuation as text events.
|
||||||
@ -225,6 +240,7 @@ struct scrcpy_options {
|
|||||||
enum sc_record_format record_format;
|
enum sc_record_format record_format;
|
||||||
enum sc_keyboard_input_mode keyboard_input_mode;
|
enum sc_keyboard_input_mode keyboard_input_mode;
|
||||||
enum sc_mouse_input_mode mouse_input_mode;
|
enum sc_mouse_input_mode mouse_input_mode;
|
||||||
|
enum sc_gamepad_input_mode gamepad_input_mode;
|
||||||
struct sc_mouse_bindings mouse_bindings;
|
struct sc_mouse_bindings mouse_bindings;
|
||||||
enum sc_camera_facing camera_facing;
|
enum sc_camera_facing camera_facing;
|
||||||
struct sc_port_range port_range;
|
struct sc_port_range port_range;
|
||||||
@ -234,7 +250,7 @@ struct scrcpy_options {
|
|||||||
uint16_t max_size;
|
uint16_t max_size;
|
||||||
uint32_t video_bit_rate;
|
uint32_t video_bit_rate;
|
||||||
uint32_t audio_bit_rate;
|
uint32_t audio_bit_rate;
|
||||||
uint16_t max_fps;
|
const char *max_fps; // float to be parsed by the server
|
||||||
enum sc_lock_video_orientation lock_video_orientation;
|
enum sc_lock_video_orientation lock_video_orientation;
|
||||||
enum sc_orientation display_orientation;
|
enum sc_orientation display_orientation;
|
||||||
enum sc_orientation record_orientation;
|
enum sc_orientation record_orientation;
|
||||||
@ -290,6 +306,8 @@ struct scrcpy_options {
|
|||||||
#define SC_OPTION_LIST_CAMERA_SIZES 0x8
|
#define SC_OPTION_LIST_CAMERA_SIZES 0x8
|
||||||
uint8_t list;
|
uint8_t list;
|
||||||
bool window;
|
bool window;
|
||||||
|
bool mouse_hover;
|
||||||
|
bool audio_dup;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern const struct scrcpy_options scrcpy_options_default;
|
extern const struct scrcpy_options scrcpy_options_default;
|
||||||
|
@ -6,8 +6,17 @@
|
|||||||
#include <SDL2/SDL_clipboard.h>
|
#include <SDL2/SDL_clipboard.h>
|
||||||
|
|
||||||
#include "device_msg.h"
|
#include "device_msg.h"
|
||||||
|
#include "events.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/str.h"
|
#include "util/str.h"
|
||||||
|
#include "util/thread.h"
|
||||||
|
|
||||||
|
struct sc_uhid_output_task_data {
|
||||||
|
struct sc_uhid_devices *uhid_devices;
|
||||||
|
uint16_t id;
|
||||||
|
uint16_t size;
|
||||||
|
uint8_t *data;
|
||||||
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket,
|
sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket,
|
||||||
@ -21,7 +30,7 @@ sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket,
|
|||||||
receiver->acksync = NULL;
|
receiver->acksync = NULL;
|
||||||
receiver->uhid_devices = NULL;
|
receiver->uhid_devices = NULL;
|
||||||
|
|
||||||
assert(cbs && cbs->on_error);
|
assert(cbs && cbs->on_ended);
|
||||||
receiver->cbs = cbs;
|
receiver->cbs = cbs;
|
||||||
receiver->cbs_userdata = cbs_userdata;
|
receiver->cbs_userdata = cbs_userdata;
|
||||||
|
|
||||||
@ -33,20 +42,52 @@ sc_receiver_destroy(struct sc_receiver *receiver) {
|
|||||||
sc_mutex_destroy(&receiver->mutex);
|
sc_mutex_destroy(&receiver->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
task_set_clipboard(void *userdata) {
|
||||||
|
assert(sc_thread_get_id() == SC_MAIN_THREAD_ID);
|
||||||
|
|
||||||
|
char *text = userdata;
|
||||||
|
|
||||||
|
char *current = SDL_GetClipboardText();
|
||||||
|
bool same = current && !strcmp(current, text);
|
||||||
|
SDL_free(current);
|
||||||
|
if (same) {
|
||||||
|
LOGD("Computer clipboard unchanged");
|
||||||
|
} else {
|
||||||
|
LOGI("Device clipboard copied");
|
||||||
|
SDL_SetClipboardText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
task_uhid_output(void *userdata) {
|
||||||
|
assert(sc_thread_get_id() == SC_MAIN_THREAD_ID);
|
||||||
|
|
||||||
|
struct sc_uhid_output_task_data *data = userdata;
|
||||||
|
|
||||||
|
sc_uhid_devices_process_hid_output(data->uhid_devices, data->id, data->data,
|
||||||
|
data->size);
|
||||||
|
|
||||||
|
free(data->data);
|
||||||
|
free(data);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
process_msg(struct sc_receiver *receiver, struct sc_device_msg *msg) {
|
process_msg(struct sc_receiver *receiver, struct sc_device_msg *msg) {
|
||||||
switch (msg->type) {
|
switch (msg->type) {
|
||||||
case DEVICE_MSG_TYPE_CLIPBOARD: {
|
case DEVICE_MSG_TYPE_CLIPBOARD: {
|
||||||
char *current = SDL_GetClipboardText();
|
// Take ownership of the text (do not destroy the msg)
|
||||||
bool same = current && !strcmp(current, msg->clipboard.text);
|
char *text = msg->clipboard.text;
|
||||||
SDL_free(current);
|
|
||||||
if (same) {
|
bool ok = sc_post_to_main_thread(task_set_clipboard, text);
|
||||||
LOGD("Computer clipboard unchanged");
|
if (!ok) {
|
||||||
|
LOGW("Could not post clipboard to main thread");
|
||||||
|
free(text);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGI("Device clipboard copied");
|
|
||||||
SDL_SetClipboardText(msg->clipboard.text);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DEVICE_MSG_TYPE_ACK_CLIPBOARD:
|
case DEVICE_MSG_TYPE_ACK_CLIPBOARD:
|
||||||
@ -64,6 +105,7 @@ process_msg(struct sc_receiver *receiver, struct sc_device_msg *msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sc_acksync_ack(receiver->acksync, msg->ack_clipboard.sequence);
|
sc_acksync_ack(receiver->acksync, msg->ack_clipboard.sequence);
|
||||||
|
// No allocation to free in the msg
|
||||||
break;
|
break;
|
||||||
case DEVICE_MSG_TYPE_UHID_OUTPUT:
|
case DEVICE_MSG_TYPE_UHID_OUTPUT:
|
||||||
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
|
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
|
||||||
@ -79,26 +121,35 @@ process_msg(struct sc_receiver *receiver, struct sc_device_msg *msg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a programming error to receive this message if there is
|
|
||||||
// no uhid_devices instance
|
|
||||||
assert(receiver->uhid_devices);
|
|
||||||
|
|
||||||
// Also check at runtime (do not trust the server)
|
|
||||||
if (!receiver->uhid_devices) {
|
if (!receiver->uhid_devices) {
|
||||||
LOGE("Received unexpected HID output message");
|
LOGE("Received unexpected HID output message");
|
||||||
|
sc_device_msg_destroy(msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_uhid_receiver *uhid_receiver =
|
struct sc_uhid_output_task_data *data = malloc(sizeof(*data));
|
||||||
sc_uhid_devices_get_receiver(receiver->uhid_devices,
|
if (!data) {
|
||||||
msg->uhid_output.id);
|
LOG_OOM();
|
||||||
if (uhid_receiver) {
|
return;
|
||||||
uhid_receiver->ops->process_output(uhid_receiver,
|
|
||||||
msg->uhid_output.data,
|
|
||||||
msg->uhid_output.size);
|
|
||||||
} else {
|
|
||||||
LOGW("No UHID receiver for id %" PRIu16, msg->uhid_output.id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// It is guaranteed that these pointers will still be valid when
|
||||||
|
// the main thread will process them (the main thread will stop
|
||||||
|
// processing SC_EVENT_RUN_ON_MAIN_THREAD on exit, when everything
|
||||||
|
// gets deinitialized)
|
||||||
|
data->uhid_devices = receiver->uhid_devices;
|
||||||
|
data->id = msg->uhid_output.id;
|
||||||
|
data->data = msg->uhid_output.data; // take ownership
|
||||||
|
data->size = msg->uhid_output.size;
|
||||||
|
|
||||||
|
bool ok = sc_post_to_main_thread(task_uhid_output, data);
|
||||||
|
if (!ok) {
|
||||||
|
LOGW("Could not post UHID output to main thread");
|
||||||
|
free(data->data);
|
||||||
|
free(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,7 +168,7 @@ process_msgs(struct sc_receiver *receiver, const uint8_t *buf, size_t len) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
process_msg(receiver, &msg);
|
process_msg(receiver, &msg);
|
||||||
sc_device_msg_destroy(&msg);
|
// the device msg must be destroyed by process_msg()
|
||||||
|
|
||||||
head += r;
|
head += r;
|
||||||
assert(head <= len);
|
assert(head <= len);
|
||||||
@ -134,12 +185,15 @@ run_receiver(void *data) {
|
|||||||
static uint8_t buf[DEVICE_MSG_MAX_SIZE];
|
static uint8_t buf[DEVICE_MSG_MAX_SIZE];
|
||||||
size_t head = 0;
|
size_t head = 0;
|
||||||
|
|
||||||
|
bool error = false;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
assert(head < DEVICE_MSG_MAX_SIZE);
|
assert(head < DEVICE_MSG_MAX_SIZE);
|
||||||
ssize_t r = net_recv(receiver->control_socket, buf + head,
|
ssize_t r = net_recv(receiver->control_socket, buf + head,
|
||||||
DEVICE_MSG_MAX_SIZE - head);
|
DEVICE_MSG_MAX_SIZE - head);
|
||||||
if (r <= 0) {
|
if (r <= 0) {
|
||||||
LOGD("Receiver stopped");
|
LOGD("Receiver stopped");
|
||||||
|
// device disconnected: keep error=false
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,6 +201,7 @@ run_receiver(void *data) {
|
|||||||
ssize_t consumed = process_msgs(receiver, buf, head);
|
ssize_t consumed = process_msgs(receiver, buf, head);
|
||||||
if (consumed == -1) {
|
if (consumed == -1) {
|
||||||
// an error occurred
|
// an error occurred
|
||||||
|
error = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,7 +212,7 @@ run_receiver(void *data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
receiver->cbs->on_error(receiver, receiver->cbs_userdata);
|
receiver->cbs->on_ended(receiver, error, receiver->cbs_userdata);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ struct sc_receiver {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct sc_receiver_callbacks {
|
struct sc_receiver_callbacks {
|
||||||
void (*on_error)(struct sc_receiver *receiver, void *userdata);
|
void (*on_ended)(struct sc_receiver *receiver, bool error, void *userdata);
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
174
app/src/scrcpy.c
174
app/src/scrcpy.c
@ -25,10 +25,12 @@
|
|||||||
#include "recorder.h"
|
#include "recorder.h"
|
||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
|
#include "uhid/gamepad_uhid.h"
|
||||||
#include "uhid/keyboard_uhid.h"
|
#include "uhid/keyboard_uhid.h"
|
||||||
#include "uhid/mouse_uhid.h"
|
#include "uhid/mouse_uhid.h"
|
||||||
#ifdef HAVE_USB
|
#ifdef HAVE_USB
|
||||||
# include "usb/aoa_hid.h"
|
# include "usb/aoa_hid.h"
|
||||||
|
# include "usb/gamepad_aoa.h"
|
||||||
# include "usb/keyboard_aoa.h"
|
# include "usb/keyboard_aoa.h"
|
||||||
# include "usb/mouse_aoa.h"
|
# include "usb/mouse_aoa.h"
|
||||||
# include "usb/usb.h"
|
# include "usb/usb.h"
|
||||||
@ -63,8 +65,8 @@ struct scrcpy {
|
|||||||
struct sc_aoa aoa;
|
struct sc_aoa aoa;
|
||||||
// sequence/ack helper to synchronize clipboard and Ctrl+v via HID
|
// sequence/ack helper to synchronize clipboard and Ctrl+v via HID
|
||||||
struct sc_acksync acksync;
|
struct sc_acksync acksync;
|
||||||
struct sc_uhid_devices uhid_devices;
|
|
||||||
#endif
|
#endif
|
||||||
|
struct sc_uhid_devices uhid_devices;
|
||||||
union {
|
union {
|
||||||
struct sc_keyboard_sdk keyboard_sdk;
|
struct sc_keyboard_sdk keyboard_sdk;
|
||||||
struct sc_keyboard_uhid keyboard_uhid;
|
struct sc_keyboard_uhid keyboard_uhid;
|
||||||
@ -77,27 +79,21 @@ struct scrcpy {
|
|||||||
struct sc_mouse_uhid mouse_uhid;
|
struct sc_mouse_uhid mouse_uhid;
|
||||||
#ifdef HAVE_USB
|
#ifdef HAVE_USB
|
||||||
struct sc_mouse_aoa mouse_aoa;
|
struct sc_mouse_aoa mouse_aoa;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
union {
|
||||||
|
struct sc_gamepad_uhid gamepad_uhid;
|
||||||
|
#ifdef HAVE_USB
|
||||||
|
struct sc_gamepad_aoa gamepad_aoa;
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
struct sc_timeout timeout;
|
struct sc_timeout timeout;
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline void
|
|
||||||
push_event(uint32_t type, const char *name) {
|
|
||||||
SDL_Event event;
|
|
||||||
event.type = type;
|
|
||||||
int ret = SDL_PushEvent(&event);
|
|
||||||
if (ret < 0) {
|
|
||||||
LOGE("Could not post %s event: %s", name, SDL_GetError());
|
|
||||||
// What could we do?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#define PUSH_EVENT(TYPE) push_event(TYPE, # TYPE)
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
static BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) {
|
static BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) {
|
||||||
if (ctrl_type == CTRL_C_EVENT) {
|
if (ctrl_type == CTRL_C_EVENT) {
|
||||||
PUSH_EVENT(SDL_QUIT);
|
sc_push_event(SDL_QUIT);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
return FALSE;
|
return FALSE;
|
||||||
@ -140,6 +136,10 @@ sdl_set_hints(const char *render_driver) {
|
|||||||
if (!SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0")) {
|
if (!SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0")) {
|
||||||
LOGW("Could not disable minimize on focus loss");
|
LOGW("Could not disable minimize on focus loss");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1")) {
|
||||||
|
LOGW("Could not allow joystick background events");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -180,12 +180,21 @@ event_loop(struct scrcpy *s) {
|
|||||||
case SC_EVENT_RECORDER_ERROR:
|
case SC_EVENT_RECORDER_ERROR:
|
||||||
LOGE("Recorder error");
|
LOGE("Recorder error");
|
||||||
return SCRCPY_EXIT_FAILURE;
|
return SCRCPY_EXIT_FAILURE;
|
||||||
|
case SC_EVENT_AOA_OPEN_ERROR:
|
||||||
|
LOGE("AOA open error");
|
||||||
|
return SCRCPY_EXIT_FAILURE;
|
||||||
case SC_EVENT_TIME_LIMIT_REACHED:
|
case SC_EVENT_TIME_LIMIT_REACHED:
|
||||||
LOGI("Time limit reached");
|
LOGI("Time limit reached");
|
||||||
return SCRCPY_EXIT_SUCCESS;
|
return SCRCPY_EXIT_SUCCESS;
|
||||||
case SDL_QUIT:
|
case SDL_QUIT:
|
||||||
LOGD("User requested to quit");
|
LOGD("User requested to quit");
|
||||||
return SCRCPY_EXIT_SUCCESS;
|
return SCRCPY_EXIT_SUCCESS;
|
||||||
|
case SC_EVENT_RUN_ON_MAIN_THREAD: {
|
||||||
|
sc_runnable_fn run = event.user.data1;
|
||||||
|
void *userdata = event.user.data2;
|
||||||
|
run(userdata);
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
if (!sc_screen_handle_event(&s->screen, &event)) {
|
if (!sc_screen_handle_event(&s->screen, &event)) {
|
||||||
return SCRCPY_EXIT_FAILURE;
|
return SCRCPY_EXIT_FAILURE;
|
||||||
@ -196,6 +205,21 @@ event_loop(struct scrcpy *s) {
|
|||||||
return SCRCPY_EXIT_FAILURE;
|
return SCRCPY_EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
terminate_event_loop(void) {
|
||||||
|
sc_reject_new_runnables();
|
||||||
|
|
||||||
|
SDL_Event event;
|
||||||
|
while (SDL_PollEvent(&event)) {
|
||||||
|
if (event.type == SC_EVENT_RUN_ON_MAIN_THREAD) {
|
||||||
|
// Make sure all posted runnables are run, to avoid memory leaks
|
||||||
|
sc_runnable_fn run = event.user.data1;
|
||||||
|
void *userdata = event.user.data2;
|
||||||
|
run(userdata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Return true on success, false on error
|
// Return true on success, false on error
|
||||||
static bool
|
static bool
|
||||||
await_for_server(bool *connected) {
|
await_for_server(bool *connected) {
|
||||||
@ -230,7 +254,7 @@ sc_recorder_on_ended(struct sc_recorder *recorder, bool success,
|
|||||||
(void) userdata;
|
(void) userdata;
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
PUSH_EVENT(SC_EVENT_RECORDER_ERROR);
|
sc_push_event(SC_EVENT_RECORDER_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,9 +268,9 @@ sc_video_demuxer_on_ended(struct sc_demuxer *demuxer,
|
|||||||
assert(status != SC_DEMUXER_STATUS_DISABLED);
|
assert(status != SC_DEMUXER_STATUS_DISABLED);
|
||||||
|
|
||||||
if (status == SC_DEMUXER_STATUS_EOS) {
|
if (status == SC_DEMUXER_STATUS_EOS) {
|
||||||
PUSH_EVENT(SC_EVENT_DEVICE_DISCONNECTED);
|
sc_push_event(SC_EVENT_DEVICE_DISCONNECTED);
|
||||||
} else {
|
} else {
|
||||||
PUSH_EVENT(SC_EVENT_DEMUXER_ERROR);
|
sc_push_event(SC_EVENT_DEMUXER_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,22 +284,27 @@ sc_audio_demuxer_on_ended(struct sc_demuxer *demuxer,
|
|||||||
// Contrary to the video demuxer, keep mirroring if only the audio fails
|
// Contrary to the video demuxer, keep mirroring if only the audio fails
|
||||||
// (unless --require-audio is set).
|
// (unless --require-audio is set).
|
||||||
if (status == SC_DEMUXER_STATUS_EOS) {
|
if (status == SC_DEMUXER_STATUS_EOS) {
|
||||||
PUSH_EVENT(SC_EVENT_DEVICE_DISCONNECTED);
|
sc_push_event(SC_EVENT_DEVICE_DISCONNECTED);
|
||||||
} else if (status == SC_DEMUXER_STATUS_ERROR
|
} else if (status == SC_DEMUXER_STATUS_ERROR
|
||||||
|| (status == SC_DEMUXER_STATUS_DISABLED
|
|| (status == SC_DEMUXER_STATUS_DISABLED
|
||||||
&& options->require_audio)) {
|
&& options->require_audio)) {
|
||||||
PUSH_EVENT(SC_EVENT_DEMUXER_ERROR);
|
sc_push_event(SC_EVENT_DEMUXER_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_controller_on_error(struct sc_controller *controller, void *userdata) {
|
sc_controller_on_ended(struct sc_controller *controller, bool error,
|
||||||
|
void *userdata) {
|
||||||
// Note: this function may be called twice, once from the controller thread
|
// Note: this function may be called twice, once from the controller thread
|
||||||
// and once from the receiver thread
|
// and once from the receiver thread
|
||||||
(void) controller;
|
(void) controller;
|
||||||
(void) userdata;
|
(void) userdata;
|
||||||
|
|
||||||
PUSH_EVENT(SC_EVENT_CONTROLLER_ERROR);
|
if (error) {
|
||||||
|
sc_push_event(SC_EVENT_CONTROLLER_ERROR);
|
||||||
|
} else {
|
||||||
|
sc_push_event(SC_EVENT_DEVICE_DISCONNECTED);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -283,7 +312,7 @@ sc_server_on_connection_failed(struct sc_server *server, void *userdata) {
|
|||||||
(void) server;
|
(void) server;
|
||||||
(void) userdata;
|
(void) userdata;
|
||||||
|
|
||||||
PUSH_EVENT(SC_EVENT_SERVER_CONNECTION_FAILED);
|
sc_push_event(SC_EVENT_SERVER_CONNECTION_FAILED);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -291,7 +320,7 @@ sc_server_on_connected(struct sc_server *server, void *userdata) {
|
|||||||
(void) server;
|
(void) server;
|
||||||
(void) userdata;
|
(void) userdata;
|
||||||
|
|
||||||
PUSH_EVENT(SC_EVENT_SERVER_CONNECTED);
|
sc_push_event(SC_EVENT_SERVER_CONNECTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -309,7 +338,7 @@ sc_timeout_on_timeout(struct sc_timeout *timeout, void *userdata) {
|
|||||||
(void) timeout;
|
(void) timeout;
|
||||||
(void) userdata;
|
(void) userdata;
|
||||||
|
|
||||||
PUSH_EVENT(SC_EVENT_TIME_LIMIT_REACHED);
|
sc_push_event(SC_EVENT_TIME_LIMIT_REACHED);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a scrcpy id to differentiate multiple running scrcpy instances
|
// Generate a scrcpy id to differentiate multiple running scrcpy instances
|
||||||
@ -321,6 +350,21 @@ scrcpy_generate_scid(void) {
|
|||||||
return sc_rand_u32(&rand) & 0x7FFFFFFF;
|
return sc_rand_u32(&rand) & 0x7FFFFFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
init_sdl_gamepads(void) {
|
||||||
|
// Trigger a SDL_CONTROLLERDEVICEADDED event for all gamepads already
|
||||||
|
// connected
|
||||||
|
int num_joysticks = SDL_NumJoysticks();
|
||||||
|
for (int i = 0; i < num_joysticks; ++i) {
|
||||||
|
if (SDL_IsGameController(i)) {
|
||||||
|
SDL_Event event;
|
||||||
|
event.cdevice.type = SDL_CONTROLLERDEVICEADDED;
|
||||||
|
event.cdevice.which = i;
|
||||||
|
SDL_PushEvent(&event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum scrcpy_exit_code
|
enum scrcpy_exit_code
|
||||||
scrcpy(struct scrcpy_options *options) {
|
scrcpy(struct scrcpy_options *options) {
|
||||||
static struct scrcpy scrcpy;
|
static struct scrcpy scrcpy;
|
||||||
@ -353,6 +397,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
bool aoa_hid_initialized = false;
|
bool aoa_hid_initialized = false;
|
||||||
bool keyboard_aoa_initialized = false;
|
bool keyboard_aoa_initialized = false;
|
||||||
bool mouse_aoa_initialized = false;
|
bool mouse_aoa_initialized = false;
|
||||||
|
bool gamepad_aoa_initialized = false;
|
||||||
#endif
|
#endif
|
||||||
bool controller_initialized = false;
|
bool controller_initialized = false;
|
||||||
bool controller_started = false;
|
bool controller_started = false;
|
||||||
@ -361,7 +406,6 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
bool timeout_started = false;
|
bool timeout_started = false;
|
||||||
|
|
||||||
struct sc_acksync *acksync = NULL;
|
struct sc_acksync *acksync = NULL;
|
||||||
struct sc_uhid_devices *uhid_devices = NULL;
|
|
||||||
|
|
||||||
uint32_t scid = scrcpy_generate_scid();
|
uint32_t scid = scrcpy_generate_scid();
|
||||||
|
|
||||||
@ -389,6 +433,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
.display_id = options->display_id,
|
.display_id = options->display_id,
|
||||||
.video = options->video,
|
.video = options->video,
|
||||||
.audio = options->audio,
|
.audio = options->audio,
|
||||||
|
.audio_dup = options->audio_dup,
|
||||||
.show_touches = options->show_touches,
|
.show_touches = options->show_touches,
|
||||||
.stay_awake = options->stay_awake,
|
.stay_awake = options->stay_awake,
|
||||||
.video_codec_options = options->video_codec_options,
|
.video_codec_options = options->video_codec_options,
|
||||||
@ -467,6 +512,13 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options->gamepad_input_mode != SC_GAMEPAD_INPUT_MODE_DISABLED) {
|
||||||
|
if (SDL_Init(SDL_INIT_GAMECONTROLLER)) {
|
||||||
|
LOGE("Could not initialize SDL gamepad: %s", SDL_GetError());
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sdl_configure(options->video_playback, options->disable_screensaver);
|
sdl_configure(options->video_playback, options->disable_screensaver);
|
||||||
|
|
||||||
// Await for server without blocking Ctrl+C handling
|
// Await for server without blocking Ctrl+C handling
|
||||||
@ -564,10 +616,11 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
struct sc_controller *controller = NULL;
|
struct sc_controller *controller = NULL;
|
||||||
struct sc_key_processor *kp = NULL;
|
struct sc_key_processor *kp = NULL;
|
||||||
struct sc_mouse_processor *mp = NULL;
|
struct sc_mouse_processor *mp = NULL;
|
||||||
|
struct sc_gamepad_processor *gp = NULL;
|
||||||
|
|
||||||
if (options->control) {
|
if (options->control) {
|
||||||
static const struct sc_controller_callbacks controller_cbs = {
|
static const struct sc_controller_callbacks controller_cbs = {
|
||||||
.on_error = sc_controller_on_error,
|
.on_ended = sc_controller_on_ended,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!sc_controller_init(&s->controller, s->server.control_socket,
|
if (!sc_controller_init(&s->controller, s->server.control_socket,
|
||||||
@ -583,7 +636,9 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA;
|
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA;
|
||||||
bool use_mouse_aoa =
|
bool use_mouse_aoa =
|
||||||
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA;
|
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA;
|
||||||
if (use_keyboard_aoa || use_mouse_aoa) {
|
bool use_gamepad_aoa =
|
||||||
|
options->gamepad_input_mode == SC_GAMEPAD_INPUT_MODE_AOA;
|
||||||
|
if (use_keyboard_aoa || use_mouse_aoa || use_gamepad_aoa) {
|
||||||
bool ok = sc_acksync_init(&s->acksync);
|
bool ok = sc_acksync_init(&s->acksync);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
goto end;
|
goto end;
|
||||||
@ -626,12 +681,15 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool aoa_fail = false;
|
||||||
if (use_keyboard_aoa) {
|
if (use_keyboard_aoa) {
|
||||||
if (sc_keyboard_aoa_init(&s->keyboard_aoa, &s->aoa)) {
|
if (sc_keyboard_aoa_init(&s->keyboard_aoa, &s->aoa)) {
|
||||||
keyboard_aoa_initialized = true;
|
keyboard_aoa_initialized = true;
|
||||||
kp = &s->keyboard_aoa.key_processor;
|
kp = &s->keyboard_aoa.key_processor;
|
||||||
} else {
|
} else {
|
||||||
LOGE("Could not initialize HID keyboard");
|
LOGE("Could not initialize HID keyboard");
|
||||||
|
aoa_fail = true;
|
||||||
|
goto aoa_complete;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -641,12 +699,19 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
mp = &s->mouse_aoa.mouse_processor;
|
mp = &s->mouse_aoa.mouse_processor;
|
||||||
} else {
|
} else {
|
||||||
LOGE("Could not initialized HID mouse");
|
LOGE("Could not initialized HID mouse");
|
||||||
|
aoa_fail = true;
|
||||||
|
goto aoa_complete;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool need_aoa = keyboard_aoa_initialized || mouse_aoa_initialized;
|
if (use_gamepad_aoa) {
|
||||||
|
sc_gamepad_aoa_init(&s->gamepad_aoa, &s->aoa);
|
||||||
|
gp = &s->gamepad_aoa.gamepad_processor;
|
||||||
|
gamepad_aoa_initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!need_aoa || !sc_aoa_start(&s->aoa)) {
|
aoa_complete:
|
||||||
|
if (aoa_fail || !sc_aoa_start(&s->aoa)) {
|
||||||
sc_acksync_destroy(&s->acksync);
|
sc_acksync_destroy(&s->acksync);
|
||||||
sc_usb_disconnect(&s->usb);
|
sc_usb_disconnect(&s->usb);
|
||||||
sc_usb_destroy(&s->usb);
|
sc_usb_destroy(&s->usb);
|
||||||
@ -663,6 +728,8 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
assert(options->mouse_input_mode != SC_MOUSE_INPUT_MODE_AOA);
|
assert(options->mouse_input_mode != SC_MOUSE_INPUT_MODE_AOA);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
struct sc_keyboard_uhid *uhid_keyboard = NULL;
|
||||||
|
|
||||||
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_SDK) {
|
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_SDK) {
|
||||||
sc_keyboard_sdk_init(&s->keyboard_sdk, &s->controller,
|
sc_keyboard_sdk_init(&s->keyboard_sdk, &s->controller,
|
||||||
options->key_inject_mode,
|
options->key_inject_mode,
|
||||||
@ -670,18 +737,17 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
kp = &s->keyboard_sdk.key_processor;
|
kp = &s->keyboard_sdk.key_processor;
|
||||||
} else if (options->keyboard_input_mode
|
} else if (options->keyboard_input_mode
|
||||||
== SC_KEYBOARD_INPUT_MODE_UHID) {
|
== SC_KEYBOARD_INPUT_MODE_UHID) {
|
||||||
sc_uhid_devices_init(&s->uhid_devices);
|
bool ok = sc_keyboard_uhid_init(&s->keyboard_uhid, &s->controller);
|
||||||
bool ok = sc_keyboard_uhid_init(&s->keyboard_uhid, &s->controller,
|
|
||||||
&s->uhid_devices);
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
uhid_devices = &s->uhid_devices;
|
|
||||||
kp = &s->keyboard_uhid.key_processor;
|
kp = &s->keyboard_uhid.key_processor;
|
||||||
|
uhid_keyboard = &s->keyboard_uhid;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK) {
|
if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK) {
|
||||||
sc_mouse_sdk_init(&s->mouse_sdk, &s->controller);
|
sc_mouse_sdk_init(&s->mouse_sdk, &s->controller,
|
||||||
|
options->mouse_hover);
|
||||||
mp = &s->mouse_sdk.mouse_processor;
|
mp = &s->mouse_sdk.mouse_processor;
|
||||||
} else if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_UHID) {
|
} else if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_UHID) {
|
||||||
bool ok = sc_mouse_uhid_init(&s->mouse_uhid, &s->controller);
|
bool ok = sc_mouse_uhid_init(&s->mouse_uhid, &s->controller);
|
||||||
@ -691,6 +757,17 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
mp = &s->mouse_uhid.mouse_processor;
|
mp = &s->mouse_uhid.mouse_processor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options->gamepad_input_mode == SC_GAMEPAD_INPUT_MODE_UHID) {
|
||||||
|
sc_gamepad_uhid_init(&s->gamepad_uhid, &s->controller);
|
||||||
|
gp = &s->gamepad_uhid.gamepad_processor;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sc_uhid_devices *uhid_devices = NULL;
|
||||||
|
if (uhid_keyboard) {
|
||||||
|
sc_uhid_devices_init(&s->uhid_devices, uhid_keyboard);
|
||||||
|
uhid_devices = &s->uhid_devices;
|
||||||
|
}
|
||||||
|
|
||||||
sc_controller_configure(&s->controller, acksync, uhid_devices);
|
sc_controller_configure(&s->controller, acksync, uhid_devices);
|
||||||
|
|
||||||
if (!sc_controller_start(&s->controller)) {
|
if (!sc_controller_start(&s->controller)) {
|
||||||
@ -712,6 +789,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
.fp = fp,
|
.fp = fp,
|
||||||
.kp = kp,
|
.kp = kp,
|
||||||
.mp = mp,
|
.mp = mp,
|
||||||
|
.gp = gp,
|
||||||
.mouse_bindings = options->mouse_bindings,
|
.mouse_bindings = options->mouse_bindings,
|
||||||
.legacy_paste = options->legacy_paste,
|
.legacy_paste = options->legacy_paste,
|
||||||
.clipboard_autosync = options->clipboard_autosync,
|
.clipboard_autosync = options->clipboard_autosync,
|
||||||
@ -729,23 +807,20 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
.start_fps_counter = options->start_fps_counter,
|
.start_fps_counter = options->start_fps_counter,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_frame_source *src;
|
|
||||||
if (options->video_playback) {
|
|
||||||
src = &s->video_decoder.frame_source;
|
|
||||||
if (options->display_buffer) {
|
|
||||||
sc_delay_buffer_init(&s->display_buffer,
|
|
||||||
options->display_buffer, true);
|
|
||||||
sc_frame_source_add_sink(src, &s->display_buffer.frame_sink);
|
|
||||||
src = &s->display_buffer.frame_source;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sc_screen_init(&s->screen, &screen_params)) {
|
if (!sc_screen_init(&s->screen, &screen_params)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
screen_initialized = true;
|
screen_initialized = true;
|
||||||
|
|
||||||
if (options->video_playback) {
|
if (options->video_playback) {
|
||||||
|
struct sc_frame_source *src = &s->video_decoder.frame_source;
|
||||||
|
if (options->display_buffer) {
|
||||||
|
sc_delay_buffer_init(&s->display_buffer,
|
||||||
|
options->display_buffer, true);
|
||||||
|
sc_frame_source_add_sink(src, &s->display_buffer.frame_sink);
|
||||||
|
src = &s->display_buffer.frame_source;
|
||||||
|
}
|
||||||
|
|
||||||
sc_frame_source_add_sink(src, &s->screen.frame_sink);
|
sc_frame_source_add_sink(src, &s->screen.frame_sink);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -826,7 +901,13 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
timeout_started = true;
|
timeout_started = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options->control
|
||||||
|
&& options->gamepad_input_mode != SC_GAMEPAD_INPUT_MODE_DISABLED) {
|
||||||
|
init_sdl_gamepads();
|
||||||
|
}
|
||||||
|
|
||||||
ret = event_loop(s);
|
ret = event_loop(s);
|
||||||
|
terminate_event_loop();
|
||||||
LOGD("quit...");
|
LOGD("quit...");
|
||||||
|
|
||||||
if (options->video_playback) {
|
if (options->video_playback) {
|
||||||
@ -851,6 +932,9 @@ end:
|
|||||||
if (mouse_aoa_initialized) {
|
if (mouse_aoa_initialized) {
|
||||||
sc_mouse_aoa_destroy(&s->mouse_aoa);
|
sc_mouse_aoa_destroy(&s->mouse_aoa);
|
||||||
}
|
}
|
||||||
|
if (gamepad_aoa_initialized) {
|
||||||
|
sc_gamepad_aoa_destroy(&s->gamepad_aoa);
|
||||||
|
}
|
||||||
sc_aoa_stop(&s->aoa);
|
sc_aoa_stop(&s->aoa);
|
||||||
sc_usb_stop(&s->usb);
|
sc_usb_stop(&s->usb);
|
||||||
}
|
}
|
||||||
|
@ -299,6 +299,12 @@ sc_screen_frame_sink_open(struct sc_frame_sink *sink,
|
|||||||
|
|
||||||
struct sc_screen *screen = DOWNCAST(sink);
|
struct sc_screen *screen = DOWNCAST(sink);
|
||||||
|
|
||||||
|
if (ctx->width <= 0 || ctx->width > 0xFFFF
|
||||||
|
|| ctx->height <= 0 || ctx->height > 0xFFFF) {
|
||||||
|
LOGE("Invalid video size: %dx%d", ctx->width, ctx->height);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
assert(ctx->width > 0 && ctx->width <= 0xFFFF);
|
assert(ctx->width > 0 && ctx->width <= 0xFFFF);
|
||||||
assert(ctx->height > 0 && ctx->height <= 0xFFFF);
|
assert(ctx->height > 0 && ctx->height <= 0xFFFF);
|
||||||
// screen->frame_size is never used before the event is pushed, and the
|
// screen->frame_size is never used before the event is pushed, and the
|
||||||
@ -306,14 +312,9 @@ sc_screen_frame_sink_open(struct sc_frame_sink *sink,
|
|||||||
screen->frame_size.width = ctx->width;
|
screen->frame_size.width = ctx->width;
|
||||||
screen->frame_size.height = ctx->height;
|
screen->frame_size.height = ctx->height;
|
||||||
|
|
||||||
static SDL_Event event = {
|
|
||||||
.type = SC_EVENT_SCREEN_INIT_SIZE,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Post the event on the UI thread (the texture must be created from there)
|
// Post the event on the UI thread (the texture must be created from there)
|
||||||
int ret = SDL_PushEvent(&event);
|
bool ok = sc_push_event(SC_EVENT_SCREEN_INIT_SIZE);
|
||||||
if (ret < 0) {
|
if (!ok) {
|
||||||
LOGW("Could not post init size event: %s", SDL_GetError());
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,14 +353,9 @@ sc_screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
|
|||||||
// The SC_EVENT_NEW_FRAME triggered for the previous frame will consume
|
// The SC_EVENT_NEW_FRAME triggered for the previous frame will consume
|
||||||
// this new frame instead
|
// this new frame instead
|
||||||
} else {
|
} else {
|
||||||
static SDL_Event new_frame_event = {
|
|
||||||
.type = SC_EVENT_NEW_FRAME,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Post the event on the UI thread
|
// Post the event on the UI thread
|
||||||
int ret = SDL_PushEvent(&new_frame_event);
|
bool ok = sc_push_event(SC_EVENT_NEW_FRAME);
|
||||||
if (ret < 0) {
|
if (!ok) {
|
||||||
LOGW("Could not post new frame event: %s", SDL_GetError());
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -481,6 +477,7 @@ sc_screen_init(struct sc_screen *screen,
|
|||||||
.screen = screen,
|
.screen = screen,
|
||||||
.kp = params->kp,
|
.kp = params->kp,
|
||||||
.mp = params->mp,
|
.mp = params->mp,
|
||||||
|
.gp = params->gp,
|
||||||
.mouse_bindings = params->mouse_bindings,
|
.mouse_bindings = params->mouse_bindings,
|
||||||
.legacy_paste = params->legacy_paste,
|
.legacy_paste = params->legacy_paste,
|
||||||
.clipboard_autosync = params->clipboard_autosync,
|
.clipboard_autosync = params->clipboard_autosync,
|
||||||
|
@ -78,6 +78,7 @@ struct sc_screen_params {
|
|||||||
struct sc_file_pusher *fp;
|
struct sc_file_pusher *fp;
|
||||||
struct sc_key_processor *kp;
|
struct sc_key_processor *kp;
|
||||||
struct sc_mouse_processor *mp;
|
struct sc_mouse_processor *mp;
|
||||||
|
struct sc_gamepad_processor *gp;
|
||||||
|
|
||||||
struct sc_mouse_bindings mouse_bindings;
|
struct sc_mouse_bindings mouse_bindings;
|
||||||
bool legacy_paste;
|
bool legacy_paste;
|
||||||
|
@ -147,7 +147,7 @@ log_level_to_server_string(enum sc_log_level level) {
|
|||||||
return "error";
|
return "error";
|
||||||
default:
|
default:
|
||||||
assert(!"unexpected log level");
|
assert(!"unexpected log level");
|
||||||
return "(unknown)";
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,6 +183,7 @@ sc_server_get_codec_name(enum sc_codec codec) {
|
|||||||
case SC_CODEC_RAW:
|
case SC_CODEC_RAW:
|
||||||
return "raw";
|
return "raw";
|
||||||
default:
|
default:
|
||||||
|
assert(!"unexpected codec");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -197,10 +198,41 @@ sc_server_get_camera_facing_name(enum sc_camera_facing camera_facing) {
|
|||||||
case SC_CAMERA_FACING_EXTERNAL:
|
case SC_CAMERA_FACING_EXTERNAL:
|
||||||
return "external";
|
return "external";
|
||||||
default:
|
default:
|
||||||
|
assert(!"unexpected camera facing");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
sc_server_get_audio_source_name(enum sc_audio_source audio_source) {
|
||||||
|
switch (audio_source) {
|
||||||
|
case SC_AUDIO_SOURCE_OUTPUT:
|
||||||
|
return "output";
|
||||||
|
case SC_AUDIO_SOURCE_MIC:
|
||||||
|
return "mic";
|
||||||
|
case SC_AUDIO_SOURCE_PLAYBACK:
|
||||||
|
return "playback";
|
||||||
|
default:
|
||||||
|
assert(!"unexpected audio source");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
validate_string(const char *s) {
|
||||||
|
// The parameters values are passed as command line arguments to adb, so
|
||||||
|
// they must either be properly escaped, or they must not contain any
|
||||||
|
// special shell characters.
|
||||||
|
// Since they are not properly escaped on Windows anyway (see
|
||||||
|
// sys/win/process.c), just forbid special shell characters.
|
||||||
|
if (strpbrk(s, " ;'\"*$?&`#\\|<>[]{}()!~\r\n")) {
|
||||||
|
LOGE("Invalid server param: [%s]", s);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static sc_pid
|
static sc_pid
|
||||||
execute_server(struct sc_server *server,
|
execute_server(struct sc_server *server,
|
||||||
const struct sc_server_params *params) {
|
const struct sc_server_params *params) {
|
||||||
@ -243,6 +275,11 @@ execute_server(struct sc_server *server,
|
|||||||
} \
|
} \
|
||||||
cmd[count++] = p; \
|
cmd[count++] = p; \
|
||||||
} while(0)
|
} while(0)
|
||||||
|
#define VALIDATE_STRING(s) do { \
|
||||||
|
if (!validate_string(s)) { \
|
||||||
|
goto end; \
|
||||||
|
} \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
ADD_PARAM("scid=%08x", params->scid);
|
ADD_PARAM("scid=%08x", params->scid);
|
||||||
ADD_PARAM("log_level=%s", log_level_to_server_string(params->log_level));
|
ADD_PARAM("log_level=%s", log_level_to_server_string(params->log_level));
|
||||||
@ -271,14 +308,21 @@ execute_server(struct sc_server *server,
|
|||||||
assert(params->video_source == SC_VIDEO_SOURCE_CAMERA);
|
assert(params->video_source == SC_VIDEO_SOURCE_CAMERA);
|
||||||
ADD_PARAM("video_source=camera");
|
ADD_PARAM("video_source=camera");
|
||||||
}
|
}
|
||||||
if (params->audio_source == SC_AUDIO_SOURCE_MIC) {
|
// If audio is enabled, an "auto" audio source must have been resolved
|
||||||
ADD_PARAM("audio_source=mic");
|
assert(params->audio_source != SC_AUDIO_SOURCE_AUTO || !params->audio);
|
||||||
|
if (params->audio_source != SC_AUDIO_SOURCE_OUTPUT && params->audio) {
|
||||||
|
ADD_PARAM("audio_source=%s",
|
||||||
|
sc_server_get_audio_source_name(params->audio_source));
|
||||||
|
}
|
||||||
|
if (params->audio_dup) {
|
||||||
|
ADD_PARAM("audio_dup=true");
|
||||||
}
|
}
|
||||||
if (params->max_size) {
|
if (params->max_size) {
|
||||||
ADD_PARAM("max_size=%" PRIu16, params->max_size);
|
ADD_PARAM("max_size=%" PRIu16, params->max_size);
|
||||||
}
|
}
|
||||||
if (params->max_fps) {
|
if (params->max_fps) {
|
||||||
ADD_PARAM("max_fps=%" PRIu16, params->max_fps);
|
VALIDATE_STRING(params->max_fps);
|
||||||
|
ADD_PARAM("max_fps=%s", params->max_fps);
|
||||||
}
|
}
|
||||||
if (params->lock_video_orientation != SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
|
if (params->lock_video_orientation != SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
|
||||||
ADD_PARAM("lock_video_orientation=%" PRIi8,
|
ADD_PARAM("lock_video_orientation=%" PRIi8,
|
||||||
@ -288,6 +332,7 @@ execute_server(struct sc_server *server,
|
|||||||
ADD_PARAM("tunnel_forward=true");
|
ADD_PARAM("tunnel_forward=true");
|
||||||
}
|
}
|
||||||
if (params->crop) {
|
if (params->crop) {
|
||||||
|
VALIDATE_STRING(params->crop);
|
||||||
ADD_PARAM("crop=%s", params->crop);
|
ADD_PARAM("crop=%s", params->crop);
|
||||||
}
|
}
|
||||||
if (!params->control) {
|
if (!params->control) {
|
||||||
@ -298,9 +343,11 @@ execute_server(struct sc_server *server,
|
|||||||
ADD_PARAM("display_id=%" PRIu32, params->display_id);
|
ADD_PARAM("display_id=%" PRIu32, params->display_id);
|
||||||
}
|
}
|
||||||
if (params->camera_id) {
|
if (params->camera_id) {
|
||||||
|
VALIDATE_STRING(params->camera_id);
|
||||||
ADD_PARAM("camera_id=%s", params->camera_id);
|
ADD_PARAM("camera_id=%s", params->camera_id);
|
||||||
}
|
}
|
||||||
if (params->camera_size) {
|
if (params->camera_size) {
|
||||||
|
VALIDATE_STRING(params->camera_size);
|
||||||
ADD_PARAM("camera_size=%s", params->camera_size);
|
ADD_PARAM("camera_size=%s", params->camera_size);
|
||||||
}
|
}
|
||||||
if (params->camera_facing != SC_CAMERA_FACING_ANY) {
|
if (params->camera_facing != SC_CAMERA_FACING_ANY) {
|
||||||
@ -308,6 +355,7 @@ execute_server(struct sc_server *server,
|
|||||||
sc_server_get_camera_facing_name(params->camera_facing));
|
sc_server_get_camera_facing_name(params->camera_facing));
|
||||||
}
|
}
|
||||||
if (params->camera_ar) {
|
if (params->camera_ar) {
|
||||||
|
VALIDATE_STRING(params->camera_ar);
|
||||||
ADD_PARAM("camera_ar=%s", params->camera_ar);
|
ADD_PARAM("camera_ar=%s", params->camera_ar);
|
||||||
}
|
}
|
||||||
if (params->camera_fps) {
|
if (params->camera_fps) {
|
||||||
@ -323,15 +371,19 @@ execute_server(struct sc_server *server,
|
|||||||
ADD_PARAM("stay_awake=true");
|
ADD_PARAM("stay_awake=true");
|
||||||
}
|
}
|
||||||
if (params->video_codec_options) {
|
if (params->video_codec_options) {
|
||||||
|
VALIDATE_STRING(params->video_codec_options);
|
||||||
ADD_PARAM("video_codec_options=%s", params->video_codec_options);
|
ADD_PARAM("video_codec_options=%s", params->video_codec_options);
|
||||||
}
|
}
|
||||||
if (params->audio_codec_options) {
|
if (params->audio_codec_options) {
|
||||||
|
VALIDATE_STRING(params->audio_codec_options);
|
||||||
ADD_PARAM("audio_codec_options=%s", params->audio_codec_options);
|
ADD_PARAM("audio_codec_options=%s", params->audio_codec_options);
|
||||||
}
|
}
|
||||||
if (params->video_encoder) {
|
if (params->video_encoder) {
|
||||||
|
VALIDATE_STRING(params->video_encoder);
|
||||||
ADD_PARAM("video_encoder=%s", params->video_encoder);
|
ADD_PARAM("video_encoder=%s", params->video_encoder);
|
||||||
}
|
}
|
||||||
if (params->audio_encoder) {
|
if (params->audio_encoder) {
|
||||||
|
VALIDATE_STRING(params->audio_encoder);
|
||||||
ADD_PARAM("audio_encoder=%s", params->audio_encoder);
|
ADD_PARAM("audio_encoder=%s", params->audio_encoder);
|
||||||
}
|
}
|
||||||
if (params->power_off_on_close) {
|
if (params->power_off_on_close) {
|
||||||
@ -607,6 +659,14 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (control_socket != SC_SOCKET_NONE) {
|
||||||
|
// Disable Nagle's algorithm for the control socket
|
||||||
|
// (it only impacts the sending side, so it is useless to set it
|
||||||
|
// for the other sockets)
|
||||||
|
bool ok = net_set_tcp_nodelay(control_socket, true);
|
||||||
|
(void) ok; // error already logged
|
||||||
|
}
|
||||||
|
|
||||||
// we don't need the adb tunnel anymore
|
// we don't need the adb tunnel anymore
|
||||||
sc_adb_tunnel_close(tunnel, &server->intr, serial,
|
sc_adb_tunnel_close(tunnel, &server->intr, serial,
|
||||||
server->device_socket_name);
|
server->device_socket_name);
|
||||||
|
@ -44,12 +44,13 @@ struct sc_server_params {
|
|||||||
uint16_t max_size;
|
uint16_t max_size;
|
||||||
uint32_t video_bit_rate;
|
uint32_t video_bit_rate;
|
||||||
uint32_t audio_bit_rate;
|
uint32_t audio_bit_rate;
|
||||||
uint16_t max_fps;
|
const char *max_fps; // float to be parsed by the server
|
||||||
int8_t lock_video_orientation;
|
int8_t lock_video_orientation;
|
||||||
bool control;
|
bool control;
|
||||||
uint32_t display_id;
|
uint32_t display_id;
|
||||||
bool video;
|
bool video;
|
||||||
bool audio;
|
bool audio;
|
||||||
|
bool audio_dup;
|
||||||
bool show_touches;
|
bool show_touches;
|
||||||
bool stay_awake;
|
bool stay_awake;
|
||||||
bool force_adb_forward;
|
bool force_adb_forward;
|
||||||
|
50
app/src/trait/gamepad_processor.h
Normal file
50
app/src/trait/gamepad_processor.h
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
#ifndef SC_GAMEPAD_PROCESSOR_H
|
||||||
|
#define SC_GAMEPAD_PROCESSOR_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "input_events.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gamepad processor trait.
|
||||||
|
*
|
||||||
|
* Component able to handle gamepads devices and inject buttons and axis events.
|
||||||
|
*/
|
||||||
|
struct sc_gamepad_processor {
|
||||||
|
const struct sc_gamepad_processor_ops *ops;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sc_gamepad_processor_ops {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a gamepad device added or removed
|
||||||
|
*
|
||||||
|
* This function is mandatory.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
(*process_gamepad_device)(struct sc_gamepad_processor *gp,
|
||||||
|
const struct sc_gamepad_device_event *event);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a gamepad axis event
|
||||||
|
*
|
||||||
|
* This function is mandatory.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
(*process_gamepad_axis)(struct sc_gamepad_processor *gp,
|
||||||
|
const struct sc_gamepad_axis_event *event);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a gamepad button event
|
||||||
|
*
|
||||||
|
* This function is mandatory.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
(*process_gamepad_button)(struct sc_gamepad_processor *gp,
|
||||||
|
const struct sc_gamepad_button_event *event);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
123
app/src/uhid/gamepad_uhid.c
Normal file
123
app/src/uhid/gamepad_uhid.c
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
#include "gamepad_uhid.h"
|
||||||
|
|
||||||
|
#include "hid/hid_gamepad.h"
|
||||||
|
#include "input_events.h"
|
||||||
|
#include "util/log.h"
|
||||||
|
|
||||||
|
/** Downcast gamepad processor to sc_gamepad_uhid */
|
||||||
|
#define DOWNCAST(GP) container_of(GP, struct sc_gamepad_uhid, gamepad_processor)
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_gamepad_uhid_send_input(struct sc_gamepad_uhid *gamepad,
|
||||||
|
const struct sc_hid_input *hid_input,
|
||||||
|
const char *name) {
|
||||||
|
struct sc_control_msg msg;
|
||||||
|
msg.type = SC_CONTROL_MSG_TYPE_UHID_INPUT;
|
||||||
|
msg.uhid_input.id = hid_input->hid_id;
|
||||||
|
|
||||||
|
assert(hid_input->size <= SC_HID_MAX_SIZE);
|
||||||
|
memcpy(msg.uhid_input.data, hid_input->data, hid_input->size);
|
||||||
|
msg.uhid_input.size = hid_input->size;
|
||||||
|
|
||||||
|
if (!sc_controller_push_msg(gamepad->controller, &msg)) {
|
||||||
|
LOGE("Could not push UHID_INPUT message (%s)", name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_gamepad_uhid_send_open(struct sc_gamepad_uhid *gamepad,
|
||||||
|
const struct sc_hid_open *hid_open) {
|
||||||
|
struct sc_control_msg msg;
|
||||||
|
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
|
||||||
|
msg.uhid_create.id = hid_open->hid_id;
|
||||||
|
msg.uhid_create.name = hid_open->name;
|
||||||
|
msg.uhid_create.report_desc = hid_open->report_desc;
|
||||||
|
msg.uhid_create.report_desc_size = hid_open->report_desc_size;
|
||||||
|
|
||||||
|
if (!sc_controller_push_msg(gamepad->controller, &msg)) {
|
||||||
|
LOGE("Could not push UHID_CREATE message (gamepad)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_gamepad_uhid_send_close(struct sc_gamepad_uhid *gamepad,
|
||||||
|
const struct sc_hid_close *hid_close) {
|
||||||
|
struct sc_control_msg msg;
|
||||||
|
msg.type = SC_CONTROL_MSG_TYPE_UHID_DESTROY;
|
||||||
|
msg.uhid_create.id = hid_close->hid_id;
|
||||||
|
|
||||||
|
if (!sc_controller_push_msg(gamepad->controller, &msg)) {
|
||||||
|
LOGE("Could not push UHID_DESTROY message (gamepad)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_gamepad_processor_process_gamepad_device(struct sc_gamepad_processor *gp,
|
||||||
|
const struct sc_gamepad_device_event *event) {
|
||||||
|
struct sc_gamepad_uhid *gamepad = DOWNCAST(gp);
|
||||||
|
|
||||||
|
if (event->type == SC_GAMEPAD_DEVICE_ADDED) {
|
||||||
|
struct sc_hid_open hid_open;
|
||||||
|
if (!sc_hid_gamepad_generate_open(&gamepad->hid, &hid_open,
|
||||||
|
event->gamepad_id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_gamepad_uhid_send_open(gamepad, &hid_open);
|
||||||
|
} else {
|
||||||
|
assert(event->type == SC_GAMEPAD_DEVICE_REMOVED);
|
||||||
|
|
||||||
|
struct sc_hid_close hid_close;
|
||||||
|
if (!sc_hid_gamepad_generate_close(&gamepad->hid, &hid_close,
|
||||||
|
event->gamepad_id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_gamepad_uhid_send_close(gamepad, &hid_close);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_gamepad_processor_process_gamepad_axis(struct sc_gamepad_processor *gp,
|
||||||
|
const struct sc_gamepad_axis_event *event) {
|
||||||
|
struct sc_gamepad_uhid *gamepad = DOWNCAST(gp);
|
||||||
|
|
||||||
|
struct sc_hid_input hid_input;
|
||||||
|
if (!sc_hid_gamepad_generate_input_from_axis(&gamepad->hid, &hid_input,
|
||||||
|
event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_gamepad_uhid_send_input(gamepad, &hid_input, "gamepad axis");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_gamepad_processor_process_gamepad_button(struct sc_gamepad_processor *gp,
|
||||||
|
const struct sc_gamepad_button_event *event) {
|
||||||
|
struct sc_gamepad_uhid *gamepad = DOWNCAST(gp);
|
||||||
|
|
||||||
|
struct sc_hid_input hid_input;
|
||||||
|
if (!sc_hid_gamepad_generate_input_from_button(&gamepad->hid, &hid_input,
|
||||||
|
event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_gamepad_uhid_send_input(gamepad, &hid_input, "gamepad button");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_gamepad_uhid_init(struct sc_gamepad_uhid *gamepad,
|
||||||
|
struct sc_controller *controller) {
|
||||||
|
sc_hid_gamepad_init(&gamepad->hid);
|
||||||
|
|
||||||
|
gamepad->controller = controller;
|
||||||
|
|
||||||
|
static const struct sc_gamepad_processor_ops ops = {
|
||||||
|
.process_gamepad_device = sc_gamepad_processor_process_gamepad_device,
|
||||||
|
.process_gamepad_axis = sc_gamepad_processor_process_gamepad_axis,
|
||||||
|
.process_gamepad_button = sc_gamepad_processor_process_gamepad_button,
|
||||||
|
};
|
||||||
|
|
||||||
|
gamepad->gamepad_processor.ops = &ops;
|
||||||
|
}
|
23
app/src/uhid/gamepad_uhid.h
Normal file
23
app/src/uhid/gamepad_uhid.h
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#ifndef SC_GAMEPAD_UHID_H
|
||||||
|
#define SC_GAMEPAD_UHID_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "controller.h"
|
||||||
|
#include "hid/hid_gamepad.h"
|
||||||
|
#include "trait/gamepad_processor.h"
|
||||||
|
|
||||||
|
struct sc_gamepad_uhid {
|
||||||
|
struct sc_gamepad_processor gamepad_processor; // gamepad processor trait
|
||||||
|
|
||||||
|
struct sc_hid_gamepad hid;
|
||||||
|
struct sc_controller *controller;
|
||||||
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_gamepad_uhid_init(struct sc_gamepad_uhid *mouse,
|
||||||
|
struct sc_controller *controller);
|
||||||
|
|
||||||
|
#endif
|
@ -9,21 +9,19 @@
|
|||||||
#define DOWNCAST_RECEIVER(UR) \
|
#define DOWNCAST_RECEIVER(UR) \
|
||||||
container_of(UR, struct sc_keyboard_uhid, uhid_receiver)
|
container_of(UR, struct sc_keyboard_uhid, uhid_receiver)
|
||||||
|
|
||||||
#define UHID_KEYBOARD_ID 1
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_keyboard_uhid_send_input(struct sc_keyboard_uhid *kb,
|
sc_keyboard_uhid_send_input(struct sc_keyboard_uhid *kb,
|
||||||
const struct sc_hid_event *event) {
|
const struct sc_hid_input *hid_input) {
|
||||||
struct sc_control_msg msg;
|
struct sc_control_msg msg;
|
||||||
msg.type = SC_CONTROL_MSG_TYPE_UHID_INPUT;
|
msg.type = SC_CONTROL_MSG_TYPE_UHID_INPUT;
|
||||||
msg.uhid_input.id = UHID_KEYBOARD_ID;
|
msg.uhid_input.id = hid_input->hid_id;
|
||||||
|
|
||||||
assert(event->size <= SC_HID_MAX_SIZE);
|
assert(hid_input->size <= SC_HID_MAX_SIZE);
|
||||||
memcpy(msg.uhid_input.data, event->data, event->size);
|
memcpy(msg.uhid_input.data, hid_input->data, hid_input->size);
|
||||||
msg.uhid_input.size = event->size;
|
msg.uhid_input.size = hid_input->size;
|
||||||
|
|
||||||
if (!sc_controller_push_msg(kb->controller, &msg)) {
|
if (!sc_controller_push_msg(kb->controller, &msg)) {
|
||||||
LOGE("Could not send UHID_INPUT message (key)");
|
LOGE("Could not push UHID_INPUT message (key)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,23 +29,22 @@ static void
|
|||||||
sc_keyboard_uhid_synchronize_mod(struct sc_keyboard_uhid *kb) {
|
sc_keyboard_uhid_synchronize_mod(struct sc_keyboard_uhid *kb) {
|
||||||
SDL_Keymod sdl_mod = SDL_GetModState();
|
SDL_Keymod sdl_mod = SDL_GetModState();
|
||||||
uint16_t mod = sc_mods_state_from_sdl(sdl_mod) & (SC_MOD_CAPS | SC_MOD_NUM);
|
uint16_t mod = sc_mods_state_from_sdl(sdl_mod) & (SC_MOD_CAPS | SC_MOD_NUM);
|
||||||
|
uint16_t diff = mod ^ kb->device_mod;
|
||||||
uint16_t device_mod =
|
|
||||||
atomic_load_explicit(&kb->device_mod, memory_order_relaxed);
|
|
||||||
uint16_t diff = mod ^ device_mod;
|
|
||||||
|
|
||||||
if (diff) {
|
if (diff) {
|
||||||
// Inherently racy (the HID output reports arrive asynchronously in
|
// Inherently racy (the HID output reports arrive asynchronously in
|
||||||
// response to key presses), but will re-synchronize on next key press
|
// response to key presses), but will re-synchronize on next key press
|
||||||
// or HID output anyway
|
// or HID output anyway
|
||||||
atomic_store_explicit(&kb->device_mod, mod, memory_order_relaxed);
|
kb->device_mod = mod;
|
||||||
|
|
||||||
struct sc_hid_event hid_event;
|
struct sc_hid_input hid_input;
|
||||||
sc_hid_keyboard_event_from_mods(&hid_event, diff);
|
if (!sc_hid_keyboard_generate_input_from_mods(&hid_input, diff)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
LOGV("HID keyboard state synchronized");
|
LOGV("HID keyboard state synchronized");
|
||||||
|
|
||||||
sc_keyboard_uhid_send_input(kb, &hid_event);
|
sc_keyboard_uhid_send_input(kb, &hid_input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,6 +54,8 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
|
|||||||
uint64_t ack_to_wait) {
|
uint64_t ack_to_wait) {
|
||||||
(void) ack_to_wait;
|
(void) ack_to_wait;
|
||||||
|
|
||||||
|
assert(sc_thread_get_id() == SC_MAIN_THREAD_ID);
|
||||||
|
|
||||||
if (event->repeat) {
|
if (event->repeat) {
|
||||||
// In USB HID protocol, key repeat is handled by the host (Android), so
|
// In USB HID protocol, key repeat is handled by the host (Android), so
|
||||||
// just ignore key repeat here.
|
// just ignore key repeat here.
|
||||||
@ -65,22 +64,20 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
|
|||||||
|
|
||||||
struct sc_keyboard_uhid *kb = DOWNCAST(kp);
|
struct sc_keyboard_uhid *kb = DOWNCAST(kp);
|
||||||
|
|
||||||
struct sc_hid_event hid_event;
|
struct sc_hid_input hid_input;
|
||||||
|
|
||||||
// Not all keys are supported, just ignore unsupported keys
|
// Not all keys are supported, just ignore unsupported keys
|
||||||
if (sc_hid_keyboard_event_from_key(&kb->hid, &hid_event, event)) {
|
if (sc_hid_keyboard_generate_input_from_key(&kb->hid, &hid_input, event)) {
|
||||||
if (event->scancode == SC_SCANCODE_CAPSLOCK) {
|
if (event->scancode == SC_SCANCODE_CAPSLOCK) {
|
||||||
atomic_fetch_xor_explicit(&kb->device_mod, SC_MOD_CAPS,
|
kb->device_mod ^= SC_MOD_CAPS;
|
||||||
memory_order_relaxed);
|
|
||||||
} else if (event->scancode == SC_SCANCODE_NUMLOCK) {
|
} else if (event->scancode == SC_SCANCODE_NUMLOCK) {
|
||||||
atomic_fetch_xor_explicit(&kb->device_mod, SC_MOD_NUM,
|
kb->device_mod ^= SC_MOD_NUM;
|
||||||
memory_order_relaxed);
|
|
||||||
} else {
|
} else {
|
||||||
// Synchronize modifiers (only if the scancode itself does not
|
// Synchronize modifiers (only if the scancode itself does not
|
||||||
// change the modifiers)
|
// change the modifiers)
|
||||||
sc_keyboard_uhid_synchronize_mod(kb);
|
sc_keyboard_uhid_synchronize_mod(kb);
|
||||||
}
|
}
|
||||||
sc_keyboard_uhid_send_input(kb, &hid_event);
|
sc_keyboard_uhid_send_input(kb, &hid_input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,34 +95,31 @@ sc_keyboard_uhid_to_sc_mod(uint8_t hid_led) {
|
|||||||
return mod;
|
return mod;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
void
|
||||||
sc_uhid_receiver_process_output(struct sc_uhid_receiver *receiver,
|
sc_keyboard_uhid_process_hid_output(struct sc_keyboard_uhid *kb,
|
||||||
const uint8_t *data, size_t len) {
|
const uint8_t *data, size_t size) {
|
||||||
// Called from the thread receiving device messages
|
assert(sc_thread_get_id() == SC_MAIN_THREAD_ID);
|
||||||
|
|
||||||
assert(len);
|
assert(size);
|
||||||
|
|
||||||
// Also check at runtime (do not trust the server)
|
// Also check at runtime (do not trust the server)
|
||||||
if (!len) {
|
if (!size) {
|
||||||
LOGE("Unexpected empty HID output message");
|
LOGE("Unexpected empty HID output message");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_keyboard_uhid *kb = DOWNCAST_RECEIVER(receiver);
|
|
||||||
|
|
||||||
uint8_t hid_led = data[0];
|
uint8_t hid_led = data[0];
|
||||||
uint16_t device_mod = sc_keyboard_uhid_to_sc_mod(hid_led);
|
uint16_t device_mod = sc_keyboard_uhid_to_sc_mod(hid_led);
|
||||||
atomic_store_explicit(&kb->device_mod, device_mod, memory_order_relaxed);
|
kb->device_mod = device_mod;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
|
sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
|
||||||
struct sc_controller *controller,
|
struct sc_controller *controller) {
|
||||||
struct sc_uhid_devices *uhid_devices) {
|
|
||||||
sc_hid_keyboard_init(&kb->hid);
|
sc_hid_keyboard_init(&kb->hid);
|
||||||
|
|
||||||
kb->controller = controller;
|
kb->controller = controller;
|
||||||
atomic_init(&kb->device_mod, 0);
|
kb->device_mod = 0;
|
||||||
|
|
||||||
static const struct sc_key_processor_ops ops = {
|
static const struct sc_key_processor_ops ops = {
|
||||||
.process_key = sc_key_processor_process_key,
|
.process_key = sc_key_processor_process_key,
|
||||||
@ -140,19 +134,16 @@ sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
|
|||||||
kb->key_processor.hid = true;
|
kb->key_processor.hid = true;
|
||||||
kb->key_processor.ops = &ops;
|
kb->key_processor.ops = &ops;
|
||||||
|
|
||||||
static const struct sc_uhid_receiver_ops uhid_receiver_ops = {
|
struct sc_hid_open hid_open;
|
||||||
.process_output = sc_uhid_receiver_process_output,
|
sc_hid_keyboard_generate_open(&hid_open);
|
||||||
};
|
assert(hid_open.hid_id == SC_HID_ID_KEYBOARD);
|
||||||
|
|
||||||
kb->uhid_receiver.id = UHID_KEYBOARD_ID;
|
|
||||||
kb->uhid_receiver.ops = &uhid_receiver_ops;
|
|
||||||
sc_uhid_devices_add_receiver(uhid_devices, &kb->uhid_receiver);
|
|
||||||
|
|
||||||
struct sc_control_msg msg;
|
struct sc_control_msg msg;
|
||||||
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
|
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
|
||||||
msg.uhid_create.id = UHID_KEYBOARD_ID;
|
msg.uhid_create.id = SC_HID_ID_KEYBOARD;
|
||||||
msg.uhid_create.report_desc = SC_HID_KEYBOARD_REPORT_DESC;
|
msg.uhid_create.name = hid_open.name;
|
||||||
msg.uhid_create.report_desc_size = SC_HID_KEYBOARD_REPORT_DESC_LEN;
|
msg.uhid_create.report_desc = hid_open.report_desc;
|
||||||
|
msg.uhid_create.report_desc_size = hid_open.report_desc_size;
|
||||||
if (!sc_controller_push_msg(controller, &msg)) {
|
if (!sc_controller_push_msg(controller, &msg)) {
|
||||||
LOGE("Could not send UHID_CREATE message (keyboard)");
|
LOGE("Could not send UHID_CREATE message (keyboard)");
|
||||||
return false;
|
return false;
|
||||||
|
@ -7,21 +7,22 @@
|
|||||||
|
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "hid/hid_keyboard.h"
|
#include "hid/hid_keyboard.h"
|
||||||
#include "uhid/uhid_output.h"
|
|
||||||
#include "trait/key_processor.h"
|
#include "trait/key_processor.h"
|
||||||
|
|
||||||
struct sc_keyboard_uhid {
|
struct sc_keyboard_uhid {
|
||||||
struct sc_key_processor key_processor; // key processor trait
|
struct sc_key_processor key_processor; // key processor trait
|
||||||
struct sc_uhid_receiver uhid_receiver;
|
|
||||||
|
|
||||||
struct sc_hid_keyboard hid;
|
struct sc_hid_keyboard hid;
|
||||||
struct sc_controller *controller;
|
struct sc_controller *controller;
|
||||||
atomic_uint_least16_t device_mod;
|
uint16_t device_mod;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
|
sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
|
||||||
struct sc_controller *controller,
|
struct sc_controller *controller);
|
||||||
struct sc_uhid_devices *uhid_devices);
|
|
||||||
|
void
|
||||||
|
sc_keyboard_uhid_process_hid_output(struct sc_keyboard_uhid *kb,
|
||||||
|
const uint8_t *data, size_t size);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -7,21 +7,20 @@
|
|||||||
/** Downcast mouse processor to mouse_uhid */
|
/** Downcast mouse processor to mouse_uhid */
|
||||||
#define DOWNCAST(MP) container_of(MP, struct sc_mouse_uhid, mouse_processor)
|
#define DOWNCAST(MP) container_of(MP, struct sc_mouse_uhid, mouse_processor)
|
||||||
|
|
||||||
#define UHID_MOUSE_ID 2
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_mouse_uhid_send_input(struct sc_mouse_uhid *mouse,
|
sc_mouse_uhid_send_input(struct sc_mouse_uhid *mouse,
|
||||||
const struct sc_hid_event *event, const char *name) {
|
const struct sc_hid_input *hid_input,
|
||||||
|
const char *name) {
|
||||||
struct sc_control_msg msg;
|
struct sc_control_msg msg;
|
||||||
msg.type = SC_CONTROL_MSG_TYPE_UHID_INPUT;
|
msg.type = SC_CONTROL_MSG_TYPE_UHID_INPUT;
|
||||||
msg.uhid_input.id = UHID_MOUSE_ID;
|
msg.uhid_input.id = hid_input->hid_id;
|
||||||
|
|
||||||
assert(event->size <= SC_HID_MAX_SIZE);
|
assert(hid_input->size <= SC_HID_MAX_SIZE);
|
||||||
memcpy(msg.uhid_input.data, event->data, event->size);
|
memcpy(msg.uhid_input.data, hid_input->data, hid_input->size);
|
||||||
msg.uhid_input.size = event->size;
|
msg.uhid_input.size = hid_input->size;
|
||||||
|
|
||||||
if (!sc_controller_push_msg(mouse->controller, &msg)) {
|
if (!sc_controller_push_msg(mouse->controller, &msg)) {
|
||||||
LOGE("Could not send UHID_INPUT message (%s)", name);
|
LOGE("Could not push UHID_INPUT message (%s)", name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,10 +29,10 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
|
|||||||
const struct sc_mouse_motion_event *event) {
|
const struct sc_mouse_motion_event *event) {
|
||||||
struct sc_mouse_uhid *mouse = DOWNCAST(mp);
|
struct sc_mouse_uhid *mouse = DOWNCAST(mp);
|
||||||
|
|
||||||
struct sc_hid_event hid_event;
|
struct sc_hid_input hid_input;
|
||||||
sc_hid_mouse_event_from_motion(&hid_event, event);
|
sc_hid_mouse_generate_input_from_motion(&hid_input, event);
|
||||||
|
|
||||||
sc_mouse_uhid_send_input(mouse, &hid_event, "mouse motion");
|
sc_mouse_uhid_send_input(mouse, &hid_input, "mouse motion");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -41,10 +40,10 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
|
|||||||
const struct sc_mouse_click_event *event) {
|
const struct sc_mouse_click_event *event) {
|
||||||
struct sc_mouse_uhid *mouse = DOWNCAST(mp);
|
struct sc_mouse_uhid *mouse = DOWNCAST(mp);
|
||||||
|
|
||||||
struct sc_hid_event hid_event;
|
struct sc_hid_input hid_input;
|
||||||
sc_hid_mouse_event_from_click(&hid_event, event);
|
sc_hid_mouse_generate_input_from_click(&hid_input, event);
|
||||||
|
|
||||||
sc_mouse_uhid_send_input(mouse, &hid_event, "mouse click");
|
sc_mouse_uhid_send_input(mouse, &hid_input, "mouse click");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -52,10 +51,10 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
|
|||||||
const struct sc_mouse_scroll_event *event) {
|
const struct sc_mouse_scroll_event *event) {
|
||||||
struct sc_mouse_uhid *mouse = DOWNCAST(mp);
|
struct sc_mouse_uhid *mouse = DOWNCAST(mp);
|
||||||
|
|
||||||
struct sc_hid_event hid_event;
|
struct sc_hid_input hid_input;
|
||||||
sc_hid_mouse_event_from_scroll(&hid_event, event);
|
sc_hid_mouse_generate_input_from_scroll(&hid_input, event);
|
||||||
|
|
||||||
sc_mouse_uhid_send_input(mouse, &hid_event, "mouse scroll");
|
sc_mouse_uhid_send_input(mouse, &hid_input, "mouse scroll");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
@ -75,13 +74,18 @@ sc_mouse_uhid_init(struct sc_mouse_uhid *mouse,
|
|||||||
|
|
||||||
mouse->mouse_processor.relative_mode = true;
|
mouse->mouse_processor.relative_mode = true;
|
||||||
|
|
||||||
|
struct sc_hid_open hid_open;
|
||||||
|
sc_hid_mouse_generate_open(&hid_open);
|
||||||
|
assert(hid_open.hid_id == SC_HID_ID_MOUSE);
|
||||||
|
|
||||||
struct sc_control_msg msg;
|
struct sc_control_msg msg;
|
||||||
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
|
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
|
||||||
msg.uhid_create.id = UHID_MOUSE_ID;
|
msg.uhid_create.id = SC_HID_ID_MOUSE;
|
||||||
msg.uhid_create.report_desc = SC_HID_MOUSE_REPORT_DESC;
|
msg.uhid_create.name = hid_open.name;
|
||||||
msg.uhid_create.report_desc_size = SC_HID_MOUSE_REPORT_DESC_LEN;
|
msg.uhid_create.report_desc = hid_open.report_desc;
|
||||||
|
msg.uhid_create.report_desc_size = hid_open.report_desc_size;
|
||||||
if (!sc_controller_push_msg(controller, &msg)) {
|
if (!sc_controller_push_msg(controller, &msg)) {
|
||||||
LOGE("Could not send UHID_CREATE message (mouse)");
|
LOGE("Could not push UHID_CREATE message (mouse)");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,25 +1,27 @@
|
|||||||
#include "uhid_output.h"
|
#include "uhid_output.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
#include "uhid/keyboard_uhid.h"
|
||||||
|
#include "util/log.h"
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_uhid_devices_init(struct sc_uhid_devices *devices) {
|
sc_uhid_devices_init(struct sc_uhid_devices *devices,
|
||||||
devices->count = 0;
|
struct sc_keyboard_uhid *keyboard) {
|
||||||
|
devices->keyboard = keyboard;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_uhid_devices_add_receiver(struct sc_uhid_devices *devices,
|
sc_uhid_devices_process_hid_output(struct sc_uhid_devices *devices, uint16_t id,
|
||||||
struct sc_uhid_receiver *receiver) {
|
const uint8_t *data, size_t size) {
|
||||||
assert(devices->count < SC_UHID_MAX_RECEIVERS);
|
if (id == SC_HID_ID_KEYBOARD) {
|
||||||
devices->receivers[devices->count++] = receiver;
|
if (devices->keyboard) {
|
||||||
}
|
sc_keyboard_uhid_process_hid_output(devices->keyboard, data, size);
|
||||||
|
} else {
|
||||||
struct sc_uhid_receiver *
|
LOGW("Unexpected keyboard HID output without UHID keyboard");
|
||||||
sc_uhid_devices_get_receiver(struct sc_uhid_devices *devices, uint16_t id) {
|
|
||||||
for (size_t i = 0; i < devices->count; ++i) {
|
|
||||||
if (devices->receivers[i]->id == id) {
|
|
||||||
return devices->receivers[i];
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
LOGW("HID output ignored for id %" PRIu16, id);
|
||||||
}
|
}
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
@ -9,37 +9,19 @@
|
|||||||
/**
|
/**
|
||||||
* The communication with UHID devices is bidirectional.
|
* The communication with UHID devices is bidirectional.
|
||||||
*
|
*
|
||||||
* This component manages the registration of receivers to handle UHID output
|
* This component dispatches HID outputs to the expected processor.
|
||||||
* messages (sent from the device to the computer).
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
struct sc_uhid_receiver {
|
|
||||||
uint16_t id;
|
|
||||||
|
|
||||||
const struct sc_uhid_receiver_ops *ops;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct sc_uhid_receiver_ops {
|
|
||||||
void
|
|
||||||
(*process_output)(struct sc_uhid_receiver *receiver,
|
|
||||||
const uint8_t *data, size_t len);
|
|
||||||
};
|
|
||||||
|
|
||||||
#define SC_UHID_MAX_RECEIVERS 1
|
|
||||||
|
|
||||||
struct sc_uhid_devices {
|
struct sc_uhid_devices {
|
||||||
struct sc_uhid_receiver *receivers[SC_UHID_MAX_RECEIVERS];
|
struct sc_keyboard_uhid *keyboard;
|
||||||
unsigned count;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_uhid_devices_init(struct sc_uhid_devices *devices);
|
sc_uhid_devices_init(struct sc_uhid_devices *devices,
|
||||||
|
struct sc_keyboard_uhid *keyboard);
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_uhid_devices_add_receiver(struct sc_uhid_devices *devices,
|
sc_uhid_devices_process_hid_output(struct sc_uhid_devices *devices, uint16_t id,
|
||||||
struct sc_uhid_receiver *receiver);
|
const uint8_t *data, size_t size);
|
||||||
|
|
||||||
struct sc_uhid_receiver *
|
|
||||||
sc_uhid_devices_get_receiver(struct sc_uhid_devices *devices, uint16_t id);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <inttypes.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include "aoa_hid.h"
|
#include "aoa_hid.h"
|
||||||
|
#include "events.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/str.h"
|
#include "util/str.h"
|
||||||
|
#include "util/vector.h"
|
||||||
|
|
||||||
// See <https://source.android.com/devices/accessories/aoa2#hid-support>.
|
// See <https://source.android.com/devices/accessories/aoa2#hid-support>.
|
||||||
#define ACCESSORY_REGISTER_HID 54
|
#define ACCESSORY_REGISTER_HID 54
|
||||||
@ -15,26 +18,49 @@
|
|||||||
|
|
||||||
#define DEFAULT_TIMEOUT 1000
|
#define DEFAULT_TIMEOUT 1000
|
||||||
|
|
||||||
#define SC_AOA_EVENT_QUEUE_MAX 64
|
// Drop droppable events above this limit
|
||||||
|
#define SC_AOA_EVENT_QUEUE_LIMIT 60
|
||||||
|
|
||||||
|
struct sc_vec_hid_ids SC_VECTOR(uint16_t);
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_hid_event_log(uint16_t accessory_id, const struct sc_hid_event *event) {
|
sc_hid_input_log(const struct sc_hid_input *hid_input) {
|
||||||
// HID Event: [00] FF FF FF FF...
|
// HID input: [00] FF FF FF FF...
|
||||||
assert(event->size);
|
assert(hid_input->size);
|
||||||
char *hex = sc_str_to_hex_string(event->data, event->size);
|
char *hex = sc_str_to_hex_string(hid_input->data, hid_input->size);
|
||||||
if (!hex) {
|
if (!hex) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
LOGV("HID Event: [%d] %s", accessory_id, hex);
|
LOGV("HID input: [%" PRIu16 "] %s", hid_input->hid_id, hex);
|
||||||
free(hex);
|
free(hex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_hid_open_log(const struct sc_hid_open *hid_open) {
|
||||||
|
// HID open: [00] FF FF FF FF...
|
||||||
|
assert(hid_open->report_desc_size);
|
||||||
|
char *hex = sc_str_to_hex_string(hid_open->report_desc,
|
||||||
|
hid_open->report_desc_size);
|
||||||
|
if (!hex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOGV("HID open: [%" PRIu16 "] %s", hid_open->hid_id, hex);
|
||||||
|
free(hex);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_hid_close_log(const struct sc_hid_close *hid_close) {
|
||||||
|
// HID close: [00]
|
||||||
|
LOGV("HID close: [%" PRIu16 "]", hid_close->hid_id);
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb,
|
sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb,
|
||||||
struct sc_acksync *acksync) {
|
struct sc_acksync *acksync) {
|
||||||
sc_vecdeque_init(&aoa->queue);
|
sc_vecdeque_init(&aoa->queue);
|
||||||
|
|
||||||
if (!sc_vecdeque_reserve(&aoa->queue, SC_AOA_EVENT_QUEUE_MAX)) {
|
// Add 4 to support 4 non-droppable events without re-allocation
|
||||||
|
if (!sc_vecdeque_reserve(&aoa->queue, SC_AOA_EVENT_QUEUE_LIMIT + 4)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,38 +151,18 @@ sc_aoa_set_hid_report_desc(struct sc_aoa *aoa, uint16_t accessory_id,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
|
||||||
sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id,
|
|
||||||
const uint8_t *report_desc, uint16_t report_desc_size) {
|
|
||||||
bool ok = sc_aoa_register_hid(aoa, accessory_id, report_desc_size);
|
|
||||||
if (!ok) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ok = sc_aoa_set_hid_report_desc(aoa, accessory_id, report_desc,
|
|
||||||
report_desc_size);
|
|
||||||
if (!ok) {
|
|
||||||
if (!sc_aoa_unregister_hid(aoa, accessory_id)) {
|
|
||||||
LOGW("Could not unregister HID");
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_aoa_send_hid_event(struct sc_aoa *aoa, uint16_t accessory_id,
|
sc_aoa_send_hid_event(struct sc_aoa *aoa,
|
||||||
const struct sc_hid_event *event) {
|
const struct sc_hid_input *hid_input) {
|
||||||
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
|
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
|
||||||
uint8_t request = ACCESSORY_SEND_HID_EVENT;
|
uint8_t request = ACCESSORY_SEND_HID_EVENT;
|
||||||
// <https://source.android.com/devices/accessories/aoa2.html#hid-support>
|
// <https://source.android.com/devices/accessories/aoa2.html#hid-support>
|
||||||
// value (arg0): accessory assigned ID for the HID device
|
// value (arg0): accessory assigned ID for the HID device
|
||||||
// index (arg1): 0 (unused)
|
// index (arg1): 0 (unused)
|
||||||
uint16_t value = accessory_id;
|
uint16_t value = hid_input->hid_id;
|
||||||
uint16_t index = 0;
|
uint16_t index = 0;
|
||||||
unsigned char *data = (uint8_t *) event->data; // discard const
|
unsigned char *data = (uint8_t *) hid_input->data; // discard const
|
||||||
uint16_t length = event->size;
|
uint16_t length = hid_input->size;
|
||||||
int result = libusb_control_transfer(aoa->usb->handle, request_type,
|
int result = libusb_control_transfer(aoa->usb->handle, request_type,
|
||||||
request, value, index, data, length,
|
request, value, index, data, length,
|
||||||
DEFAULT_TIMEOUT);
|
DEFAULT_TIMEOUT);
|
||||||
@ -169,7 +175,7 @@ sc_aoa_send_hid_event(struct sc_aoa *aoa, uint16_t accessory_id,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
static bool
|
||||||
sc_aoa_unregister_hid(struct sc_aoa *aoa, uint16_t accessory_id) {
|
sc_aoa_unregister_hid(struct sc_aoa *aoa, uint16_t accessory_id) {
|
||||||
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
|
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
|
||||||
uint8_t request = ACCESSORY_UNREGISTER_HID;
|
uint8_t request = ACCESSORY_UNREGISTER_HID;
|
||||||
@ -192,41 +198,213 @@ sc_aoa_unregister_hid(struct sc_aoa *aoa, uint16_t accessory_id) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id,
|
||||||
|
const uint8_t *report_desc, uint16_t report_desc_size) {
|
||||||
|
bool ok = sc_aoa_register_hid(aoa, accessory_id, report_desc_size);
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = sc_aoa_set_hid_report_desc(aoa, accessory_id, report_desc,
|
||||||
|
report_desc_size);
|
||||||
|
if (!ok) {
|
||||||
|
if (!sc_aoa_unregister_hid(aoa, accessory_id)) {
|
||||||
|
LOGW("Could not unregister HID");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_aoa_push_hid_event_with_ack_to_wait(struct sc_aoa *aoa,
|
sc_aoa_push_input_with_ack_to_wait(struct sc_aoa *aoa,
|
||||||
uint16_t accessory_id,
|
const struct sc_hid_input *hid_input,
|
||||||
const struct sc_hid_event *event,
|
uint64_t ack_to_wait) {
|
||||||
uint64_t ack_to_wait) {
|
|
||||||
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
|
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
|
||||||
sc_hid_event_log(accessory_id, event);
|
sc_hid_input_log(hid_input);
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_mutex_lock(&aoa->mutex);
|
sc_mutex_lock(&aoa->mutex);
|
||||||
bool full = sc_vecdeque_is_full(&aoa->queue);
|
|
||||||
if (!full) {
|
bool pushed = false;
|
||||||
|
|
||||||
|
size_t size = sc_vecdeque_size(&aoa->queue);
|
||||||
|
if (size < SC_AOA_EVENT_QUEUE_LIMIT) {
|
||||||
bool was_empty = sc_vecdeque_is_empty(&aoa->queue);
|
bool was_empty = sc_vecdeque_is_empty(&aoa->queue);
|
||||||
|
|
||||||
struct sc_aoa_event *aoa_event =
|
struct sc_aoa_event *aoa_event =
|
||||||
sc_vecdeque_push_hole_noresize(&aoa->queue);
|
sc_vecdeque_push_hole_noresize(&aoa->queue);
|
||||||
aoa_event->hid = *event;
|
aoa_event->type = SC_AOA_EVENT_TYPE_INPUT;
|
||||||
aoa_event->accessory_id = accessory_id;
|
aoa_event->input.hid = *hid_input;
|
||||||
aoa_event->ack_to_wait = ack_to_wait;
|
aoa_event->input.ack_to_wait = ack_to_wait;
|
||||||
|
pushed = true;
|
||||||
|
|
||||||
if (was_empty) {
|
if (was_empty) {
|
||||||
sc_cond_signal(&aoa->event_cond);
|
sc_cond_signal(&aoa->event_cond);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Otherwise (if the queue is full), the event is discarded
|
// Otherwise, the event is discarded
|
||||||
|
|
||||||
sc_mutex_unlock(&aoa->mutex);
|
sc_mutex_unlock(&aoa->mutex);
|
||||||
|
|
||||||
return !full;
|
return pushed;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_aoa_push_open(struct sc_aoa *aoa, const struct sc_hid_open *hid_open,
|
||||||
|
bool exit_on_open_error) {
|
||||||
|
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
|
||||||
|
sc_hid_open_log(hid_open);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_mutex_lock(&aoa->mutex);
|
||||||
|
bool was_empty = sc_vecdeque_is_empty(&aoa->queue);
|
||||||
|
|
||||||
|
// an OPEN event is non-droppable, so push it to the queue even above the
|
||||||
|
// SC_AOA_EVENT_QUEUE_LIMIT
|
||||||
|
struct sc_aoa_event *aoa_event = sc_vecdeque_push_hole(&aoa->queue);
|
||||||
|
if (!aoa_event) {
|
||||||
|
LOG_OOM();
|
||||||
|
sc_mutex_unlock(&aoa->mutex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
aoa_event->type = SC_AOA_EVENT_TYPE_OPEN;
|
||||||
|
aoa_event->open.hid = *hid_open;
|
||||||
|
aoa_event->open.exit_on_error = exit_on_open_error;
|
||||||
|
|
||||||
|
if (was_empty) {
|
||||||
|
sc_cond_signal(&aoa->event_cond);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_mutex_unlock(&aoa->mutex);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_aoa_push_close(struct sc_aoa *aoa, const struct sc_hid_close *hid_close) {
|
||||||
|
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
|
||||||
|
sc_hid_close_log(hid_close);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_mutex_lock(&aoa->mutex);
|
||||||
|
bool was_empty = sc_vecdeque_is_empty(&aoa->queue);
|
||||||
|
|
||||||
|
// a CLOSE event is non-droppable, so push it to the queue even above the
|
||||||
|
// SC_AOA_EVENT_QUEUE_LIMIT
|
||||||
|
struct sc_aoa_event *aoa_event = sc_vecdeque_push_hole(&aoa->queue);
|
||||||
|
if (!aoa_event) {
|
||||||
|
LOG_OOM();
|
||||||
|
sc_mutex_unlock(&aoa->mutex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
aoa_event->type = SC_AOA_EVENT_TYPE_CLOSE;
|
||||||
|
aoa_event->close.hid = *hid_close;
|
||||||
|
|
||||||
|
if (was_empty) {
|
||||||
|
sc_cond_signal(&aoa->event_cond);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_mutex_unlock(&aoa->mutex);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
sc_aoa_process_event(struct sc_aoa *aoa, struct sc_aoa_event *event,
|
||||||
|
struct sc_vec_hid_ids *vec_open) {
|
||||||
|
switch (event->type) {
|
||||||
|
case SC_AOA_EVENT_TYPE_INPUT: {
|
||||||
|
uint64_t ack_to_wait = event->input.ack_to_wait;
|
||||||
|
if (ack_to_wait != SC_SEQUENCE_INVALID) {
|
||||||
|
LOGD("Waiting ack from server sequence=%" PRIu64_, ack_to_wait);
|
||||||
|
|
||||||
|
// If some events have ack_to_wait set, then sc_aoa must have
|
||||||
|
// been initialized with a non NULL acksync
|
||||||
|
assert(aoa->acksync);
|
||||||
|
|
||||||
|
// Do not block the loop indefinitely if the ack never comes (it
|
||||||
|
// should never happen)
|
||||||
|
sc_tick deadline = sc_tick_now() + SC_TICK_FROM_MS(500);
|
||||||
|
enum sc_acksync_wait_result result =
|
||||||
|
sc_acksync_wait(aoa->acksync, ack_to_wait, deadline);
|
||||||
|
|
||||||
|
if (result == SC_ACKSYNC_WAIT_TIMEOUT) {
|
||||||
|
LOGW("Ack not received after 500ms, discarding HID event");
|
||||||
|
// continue to process events
|
||||||
|
return true;
|
||||||
|
} else if (result == SC_ACKSYNC_WAIT_INTR) {
|
||||||
|
// stopped
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sc_hid_input *hid_input = &event->input.hid;
|
||||||
|
bool ok = sc_aoa_send_hid_event(aoa, hid_input);
|
||||||
|
if (!ok) {
|
||||||
|
LOGW("Could not send HID event to USB device: %" PRIu16,
|
||||||
|
hid_input->hid_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SC_AOA_EVENT_TYPE_OPEN: {
|
||||||
|
struct sc_hid_open *hid_open = &event->open.hid;
|
||||||
|
bool ok = sc_aoa_setup_hid(aoa, hid_open->hid_id,
|
||||||
|
hid_open->report_desc,
|
||||||
|
hid_open->report_desc_size);
|
||||||
|
if (ok) {
|
||||||
|
// The device is now open, add it to the list of devices to
|
||||||
|
// close automatically on exit
|
||||||
|
bool pushed = sc_vector_push(vec_open, hid_open->hid_id);
|
||||||
|
if (!pushed) {
|
||||||
|
LOG_OOM();
|
||||||
|
// this is not fatal, the HID device will just not be
|
||||||
|
// explicitly unregistered
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOGW("Could not open AOA device: %" PRIu16, hid_open->hid_id);
|
||||||
|
if (event->open.exit_on_error) {
|
||||||
|
// Notify the error to the main thread, which will exit
|
||||||
|
sc_push_event(SC_EVENT_AOA_OPEN_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SC_AOA_EVENT_TYPE_CLOSE: {
|
||||||
|
struct sc_hid_close *hid_close = &event->close.hid;
|
||||||
|
bool ok = sc_aoa_unregister_hid(aoa, hid_close->hid_id);
|
||||||
|
if (ok) {
|
||||||
|
// The device is not open anymore, remove it from the list of
|
||||||
|
// devices to close automatically on exit
|
||||||
|
ssize_t idx = sc_vector_index_of(vec_open, hid_close->hid_id);
|
||||||
|
if (idx >= 0) {
|
||||||
|
sc_vector_remove(vec_open, idx);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOGW("Could not close AOA device: %" PRIu16, hid_close->hid_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// continue to process events
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
run_aoa_thread(void *data) {
|
run_aoa_thread(void *data) {
|
||||||
struct sc_aoa *aoa = data;
|
struct sc_aoa *aoa = data;
|
||||||
|
|
||||||
|
// Store the HID ids of opened devices to unregister them all before exiting
|
||||||
|
struct sc_vec_hid_ids vec_open = SC_VECTOR_INITIALIZER;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
sc_mutex_lock(&aoa->mutex);
|
sc_mutex_lock(&aoa->mutex);
|
||||||
while (!aoa->stopped && sc_vecdeque_is_empty(&aoa->queue)) {
|
while (!aoa->stopped && sc_vecdeque_is_empty(&aoa->queue)) {
|
||||||
@ -240,36 +418,26 @@ run_aoa_thread(void *data) {
|
|||||||
|
|
||||||
assert(!sc_vecdeque_is_empty(&aoa->queue));
|
assert(!sc_vecdeque_is_empty(&aoa->queue));
|
||||||
struct sc_aoa_event event = sc_vecdeque_pop(&aoa->queue);
|
struct sc_aoa_event event = sc_vecdeque_pop(&aoa->queue);
|
||||||
uint64_t ack_to_wait = event.ack_to_wait;
|
|
||||||
sc_mutex_unlock(&aoa->mutex);
|
sc_mutex_unlock(&aoa->mutex);
|
||||||
|
|
||||||
if (ack_to_wait != SC_SEQUENCE_INVALID) {
|
bool cont = sc_aoa_process_event(aoa, &event, &vec_open);
|
||||||
LOGD("Waiting ack from server sequence=%" PRIu64_, ack_to_wait);
|
if (!cont) {
|
||||||
|
// stopped
|
||||||
// If some events have ack_to_wait set, then sc_aoa must have been
|
break;
|
||||||
// initialized with a non NULL acksync
|
|
||||||
assert(aoa->acksync);
|
|
||||||
|
|
||||||
// Do not block the loop indefinitely if the ack never comes (it
|
|
||||||
// should never happen)
|
|
||||||
sc_tick deadline = sc_tick_now() + SC_TICK_FROM_MS(500);
|
|
||||||
enum sc_acksync_wait_result result =
|
|
||||||
sc_acksync_wait(aoa->acksync, ack_to_wait, deadline);
|
|
||||||
|
|
||||||
if (result == SC_ACKSYNC_WAIT_TIMEOUT) {
|
|
||||||
LOGW("Ack not received after 500ms, discarding HID event");
|
|
||||||
continue;
|
|
||||||
} else if (result == SC_ACKSYNC_WAIT_INTR) {
|
|
||||||
// stopped
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ok = sc_aoa_send_hid_event(aoa, event.accessory_id, &event.hid);
|
|
||||||
if (!ok) {
|
|
||||||
LOGW("Could not send HID event to USB device");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Explicitly unregister all registered HID ids before exiting
|
||||||
|
for (size_t i = 0; i < vec_open.size; ++i) {
|
||||||
|
uint16_t hid_id = vec_open.data[i];
|
||||||
|
LOGD("Unregistering AOA device %" PRIu16 "...", hid_id);
|
||||||
|
bool ok = sc_aoa_unregister_hid(aoa, hid_id);
|
||||||
|
if (!ok) {
|
||||||
|
LOGW("Could not close AOA device: %" PRIu16, hid_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sc_vector_destroy(&vec_open);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,12 +13,27 @@
|
|||||||
#include "util/tick.h"
|
#include "util/tick.h"
|
||||||
#include "util/vecdeque.h"
|
#include "util/vecdeque.h"
|
||||||
|
|
||||||
#define SC_HID_MAX_SIZE 8
|
enum sc_aoa_event_type {
|
||||||
|
SC_AOA_EVENT_TYPE_OPEN,
|
||||||
|
SC_AOA_EVENT_TYPE_INPUT,
|
||||||
|
SC_AOA_EVENT_TYPE_CLOSE,
|
||||||
|
};
|
||||||
|
|
||||||
struct sc_aoa_event {
|
struct sc_aoa_event {
|
||||||
struct sc_hid_event hid;
|
enum sc_aoa_event_type type;
|
||||||
uint16_t accessory_id;
|
union {
|
||||||
uint64_t ack_to_wait;
|
struct {
|
||||||
|
struct sc_hid_open hid;
|
||||||
|
bool exit_on_error;
|
||||||
|
} open;
|
||||||
|
struct {
|
||||||
|
struct sc_hid_close hid;
|
||||||
|
} close;
|
||||||
|
struct {
|
||||||
|
struct sc_hid_input hid;
|
||||||
|
uint64_t ack_to_wait;
|
||||||
|
} input;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_aoa_event_queue SC_VECDEQUE(struct sc_aoa_event);
|
struct sc_aoa_event_queue SC_VECDEQUE(struct sc_aoa_event);
|
||||||
@ -49,24 +64,31 @@ sc_aoa_stop(struct sc_aoa *aoa);
|
|||||||
void
|
void
|
||||||
sc_aoa_join(struct sc_aoa *aoa);
|
sc_aoa_join(struct sc_aoa *aoa);
|
||||||
|
|
||||||
|
//bool
|
||||||
|
//sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id,
|
||||||
|
// const uint8_t *report_desc, uint16_t report_desc_size);
|
||||||
|
//
|
||||||
|
//bool
|
||||||
|
//sc_aoa_unregister_hid(struct sc_aoa *aoa, uint16_t accessory_id);
|
||||||
|
|
||||||
|
// report_desc must be a pointer to static memory, accessed at any time from
|
||||||
|
// another thread
|
||||||
bool
|
bool
|
||||||
sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id,
|
sc_aoa_push_open(struct sc_aoa *aoa, const struct sc_hid_open *hid_open,
|
||||||
const uint8_t *report_desc, uint16_t report_desc_size);
|
bool exit_on_open_error);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_aoa_unregister_hid(struct sc_aoa *aoa, uint16_t accessory_id);
|
sc_aoa_push_close(struct sc_aoa *aoa, const struct sc_hid_close *hid_close);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_aoa_push_hid_event_with_ack_to_wait(struct sc_aoa *aoa,
|
sc_aoa_push_input_with_ack_to_wait(struct sc_aoa *aoa,
|
||||||
uint16_t accessory_id,
|
const struct sc_hid_input *hid_input,
|
||||||
const struct sc_hid_event *event,
|
uint64_t ack_to_wait);
|
||||||
uint64_t ack_to_wait);
|
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
sc_aoa_push_hid_event(struct sc_aoa *aoa, uint16_t accessory_id,
|
sc_aoa_push_input(struct sc_aoa *aoa, const struct sc_hid_input *hid_input) {
|
||||||
const struct sc_hid_event *event) {
|
return sc_aoa_push_input_with_ack_to_wait(aoa, hid_input,
|
||||||
return sc_aoa_push_hid_event_with_ack_to_wait(aoa, accessory_id, event,
|
SC_SEQUENCE_INVALID);
|
||||||
SC_SEQUENCE_INVALID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
91
app/src/usb/gamepad_aoa.c
Normal file
91
app/src/usb/gamepad_aoa.c
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
#include "gamepad_aoa.h"
|
||||||
|
|
||||||
|
#include "input_events.h"
|
||||||
|
#include "util/log.h"
|
||||||
|
|
||||||
|
/** Downcast gamepad processor to gamepad_aoa */
|
||||||
|
#define DOWNCAST(GP) container_of(GP, struct sc_gamepad_aoa, gamepad_processor)
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_gamepad_processor_process_gamepad_device(struct sc_gamepad_processor *gp,
|
||||||
|
const struct sc_gamepad_device_event *event) {
|
||||||
|
struct sc_gamepad_aoa *gamepad = DOWNCAST(gp);
|
||||||
|
|
||||||
|
if (event->type == SC_GAMEPAD_DEVICE_ADDED) {
|
||||||
|
struct sc_hid_open hid_open;
|
||||||
|
if (!sc_hid_gamepad_generate_open(&gamepad->hid, &hid_open,
|
||||||
|
event->gamepad_id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// exit_on_error: false (a gamepad open failure should not exit scrcpy)
|
||||||
|
if (!sc_aoa_push_open(gamepad->aoa, &hid_open, false)) {
|
||||||
|
LOGW("Could not push AOA HID open (gamepad)");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assert(event->type == SC_GAMEPAD_DEVICE_REMOVED);
|
||||||
|
|
||||||
|
struct sc_hid_close hid_close;
|
||||||
|
if (!sc_hid_gamepad_generate_close(&gamepad->hid, &hid_close,
|
||||||
|
event->gamepad_id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sc_aoa_push_close(gamepad->aoa, &hid_close)) {
|
||||||
|
LOGW("Could not push AOA HID close (gamepad)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_gamepad_processor_process_gamepad_axis(struct sc_gamepad_processor *gp,
|
||||||
|
const struct sc_gamepad_axis_event *event) {
|
||||||
|
struct sc_gamepad_aoa *gamepad = DOWNCAST(gp);
|
||||||
|
|
||||||
|
struct sc_hid_input hid_input;
|
||||||
|
if (!sc_hid_gamepad_generate_input_from_axis(&gamepad->hid, &hid_input,
|
||||||
|
event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sc_aoa_push_input(gamepad->aoa, &hid_input)) {
|
||||||
|
LOGW("Could not push AOA HID input (gamepad axis)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_gamepad_processor_process_gamepad_button(struct sc_gamepad_processor *gp,
|
||||||
|
const struct sc_gamepad_button_event *event) {
|
||||||
|
struct sc_gamepad_aoa *gamepad = DOWNCAST(gp);
|
||||||
|
|
||||||
|
struct sc_hid_input hid_input;
|
||||||
|
if (!sc_hid_gamepad_generate_input_from_button(&gamepad->hid, &hid_input,
|
||||||
|
event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sc_aoa_push_input(gamepad->aoa, &hid_input)) {
|
||||||
|
LOGW("Could not push AOA HID input (gamepad button)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_gamepad_aoa_init(struct sc_gamepad_aoa *gamepad, struct sc_aoa *aoa) {
|
||||||
|
gamepad->aoa = aoa;
|
||||||
|
|
||||||
|
sc_hid_gamepad_init(&gamepad->hid);
|
||||||
|
|
||||||
|
static const struct sc_gamepad_processor_ops ops = {
|
||||||
|
.process_gamepad_device = sc_gamepad_processor_process_gamepad_device,
|
||||||
|
.process_gamepad_axis = sc_gamepad_processor_process_gamepad_axis,
|
||||||
|
.process_gamepad_button = sc_gamepad_processor_process_gamepad_button,
|
||||||
|
};
|
||||||
|
|
||||||
|
gamepad->gamepad_processor.ops = &ops;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_gamepad_aoa_destroy(struct sc_gamepad_aoa *gamepad) {
|
||||||
|
(void) gamepad;
|
||||||
|
// Do nothing, gamepad->aoa will automatically unregister all devices
|
||||||
|
}
|
25
app/src/usb/gamepad_aoa.h
Normal file
25
app/src/usb/gamepad_aoa.h
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#ifndef SC_GAMEPAD_AOA_H
|
||||||
|
#define SC_GAMEPAD_AOA_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "aoa_hid.h"
|
||||||
|
#include "hid/hid_gamepad.h"
|
||||||
|
#include "trait/gamepad_processor.h"
|
||||||
|
|
||||||
|
struct sc_gamepad_aoa {
|
||||||
|
struct sc_gamepad_processor gamepad_processor; // gamepad processor trait
|
||||||
|
|
||||||
|
struct sc_hid_gamepad hid;
|
||||||
|
struct sc_aoa *aoa;
|
||||||
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_gamepad_aoa_init(struct sc_gamepad_aoa *gamepad, struct sc_aoa *aoa);
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_gamepad_aoa_destroy(struct sc_gamepad_aoa *gamepad);
|
||||||
|
|
||||||
|
#endif
|
@ -8,19 +8,16 @@
|
|||||||
/** Downcast key processor to keyboard_aoa */
|
/** Downcast key processor to keyboard_aoa */
|
||||||
#define DOWNCAST(KP) container_of(KP, struct sc_keyboard_aoa, key_processor)
|
#define DOWNCAST(KP) container_of(KP, struct sc_keyboard_aoa, key_processor)
|
||||||
|
|
||||||
#define HID_KEYBOARD_ACCESSORY_ID 1
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
push_mod_lock_state(struct sc_keyboard_aoa *kb, uint16_t mods_state) {
|
push_mod_lock_state(struct sc_keyboard_aoa *kb, uint16_t mods_state) {
|
||||||
struct sc_hid_event hid_event;
|
struct sc_hid_input hid_input;
|
||||||
if (!sc_hid_keyboard_event_from_mods(&hid_event, mods_state)) {
|
if (!sc_hid_keyboard_generate_input_from_mods(&hid_input, mods_state)) {
|
||||||
// Nothing to do
|
// Nothing to do
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sc_aoa_push_hid_event(kb->aoa, HID_KEYBOARD_ACCESSORY_ID,
|
if (!sc_aoa_push_input(kb->aoa, &hid_input)) {
|
||||||
&hid_event)) {
|
LOGW("Could not push AOA HID input (mod lock state)");
|
||||||
LOGW("Could not request HID event (mod lock state)");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,10 +38,10 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
|
|||||||
|
|
||||||
struct sc_keyboard_aoa *kb = DOWNCAST(kp);
|
struct sc_keyboard_aoa *kb = DOWNCAST(kp);
|
||||||
|
|
||||||
struct sc_hid_event hid_event;
|
struct sc_hid_input hid_input;
|
||||||
|
|
||||||
// Not all keys are supported, just ignore unsupported keys
|
// Not all keys are supported, just ignore unsupported keys
|
||||||
if (sc_hid_keyboard_event_from_key(&kb->hid, &hid_event, event)) {
|
if (sc_hid_keyboard_generate_input_from_key(&kb->hid, &hid_input, event)) {
|
||||||
if (!kb->mod_lock_synchronized) {
|
if (!kb->mod_lock_synchronized) {
|
||||||
// Inject CAPSLOCK and/or NUMLOCK if necessary to synchronize
|
// Inject CAPSLOCK and/or NUMLOCK if necessary to synchronize
|
||||||
// keyboard state
|
// keyboard state
|
||||||
@ -58,11 +55,9 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
|
|||||||
// synchronization is acknowledged by the server, otherwise it could
|
// synchronization is acknowledged by the server, otherwise it could
|
||||||
// paste the old clipboard content.
|
// paste the old clipboard content.
|
||||||
|
|
||||||
if (!sc_aoa_push_hid_event_with_ack_to_wait(kb->aoa,
|
if (!sc_aoa_push_input_with_ack_to_wait(kb->aoa, &hid_input,
|
||||||
HID_KEYBOARD_ACCESSORY_ID,
|
ack_to_wait)) {
|
||||||
&hid_event,
|
LOGW("Could not push AOA HID input (key)");
|
||||||
ack_to_wait)) {
|
|
||||||
LOGW("Could not request HID event (key)");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,11 +66,12 @@ bool
|
|||||||
sc_keyboard_aoa_init(struct sc_keyboard_aoa *kb, struct sc_aoa *aoa) {
|
sc_keyboard_aoa_init(struct sc_keyboard_aoa *kb, struct sc_aoa *aoa) {
|
||||||
kb->aoa = aoa;
|
kb->aoa = aoa;
|
||||||
|
|
||||||
bool ok = sc_aoa_setup_hid(aoa, HID_KEYBOARD_ACCESSORY_ID,
|
struct sc_hid_open hid_open;
|
||||||
SC_HID_KEYBOARD_REPORT_DESC,
|
sc_hid_keyboard_generate_open(&hid_open);
|
||||||
SC_HID_KEYBOARD_REPORT_DESC_LEN);
|
|
||||||
|
bool ok = sc_aoa_push_open(aoa, &hid_open, true);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGW("Register HID keyboard failed");
|
LOGW("Could not push AOA HID open (keyboard)");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,9 +98,6 @@ sc_keyboard_aoa_init(struct sc_keyboard_aoa *kb, struct sc_aoa *aoa) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
sc_keyboard_aoa_destroy(struct sc_keyboard_aoa *kb) {
|
sc_keyboard_aoa_destroy(struct sc_keyboard_aoa *kb) {
|
||||||
// Unregister HID keyboard so the soft keyboard shows again on Android
|
(void) kb;
|
||||||
bool ok = sc_aoa_unregister_hid(kb->aoa, HID_KEYBOARD_ACCESSORY_ID);
|
// Do nothing, kb->aoa will automatically unregister all devices
|
||||||
if (!ok) {
|
|
||||||
LOGW("Could not unregister HID keyboard");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -9,19 +9,16 @@
|
|||||||
/** Downcast mouse processor to mouse_aoa */
|
/** Downcast mouse processor to mouse_aoa */
|
||||||
#define DOWNCAST(MP) container_of(MP, struct sc_mouse_aoa, mouse_processor)
|
#define DOWNCAST(MP) container_of(MP, struct sc_mouse_aoa, mouse_processor)
|
||||||
|
|
||||||
#define HID_MOUSE_ACCESSORY_ID 2
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
|
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
|
||||||
const struct sc_mouse_motion_event *event) {
|
const struct sc_mouse_motion_event *event) {
|
||||||
struct sc_mouse_aoa *mouse = DOWNCAST(mp);
|
struct sc_mouse_aoa *mouse = DOWNCAST(mp);
|
||||||
|
|
||||||
struct sc_hid_event hid_event;
|
struct sc_hid_input hid_input;
|
||||||
sc_hid_mouse_event_from_motion(&hid_event, event);
|
sc_hid_mouse_generate_input_from_motion(&hid_input, event);
|
||||||
|
|
||||||
if (!sc_aoa_push_hid_event(mouse->aoa, HID_MOUSE_ACCESSORY_ID,
|
if (!sc_aoa_push_input(mouse->aoa, &hid_input)) {
|
||||||
&hid_event)) {
|
LOGW("Could not push AOA HID input (mouse motion)");
|
||||||
LOGW("Could not request HID event (mouse motion)");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,12 +27,11 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
|
|||||||
const struct sc_mouse_click_event *event) {
|
const struct sc_mouse_click_event *event) {
|
||||||
struct sc_mouse_aoa *mouse = DOWNCAST(mp);
|
struct sc_mouse_aoa *mouse = DOWNCAST(mp);
|
||||||
|
|
||||||
struct sc_hid_event hid_event;
|
struct sc_hid_input hid_input;
|
||||||
sc_hid_mouse_event_from_click(&hid_event, event);
|
sc_hid_mouse_generate_input_from_click(&hid_input, event);
|
||||||
|
|
||||||
if (!sc_aoa_push_hid_event(mouse->aoa, HID_MOUSE_ACCESSORY_ID,
|
if (!sc_aoa_push_input(mouse->aoa, &hid_input)) {
|
||||||
&hid_event)) {
|
LOGW("Could not push AOA HID input (mouse click)");
|
||||||
LOGW("Could not request HID event (mouse click)");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,12 +40,11 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
|
|||||||
const struct sc_mouse_scroll_event *event) {
|
const struct sc_mouse_scroll_event *event) {
|
||||||
struct sc_mouse_aoa *mouse = DOWNCAST(mp);
|
struct sc_mouse_aoa *mouse = DOWNCAST(mp);
|
||||||
|
|
||||||
struct sc_hid_event hid_event;
|
struct sc_hid_input hid_input;
|
||||||
sc_hid_mouse_event_from_scroll(&hid_event, event);
|
sc_hid_mouse_generate_input_from_scroll(&hid_input, event);
|
||||||
|
|
||||||
if (!sc_aoa_push_hid_event(mouse->aoa, HID_MOUSE_ACCESSORY_ID,
|
if (!sc_aoa_push_input(mouse->aoa, &hid_input)) {
|
||||||
&hid_event)) {
|
LOGW("Could not push AOA HID input (mouse scroll)");
|
||||||
LOGW("Could not request HID event (mouse scroll)");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,11 +52,12 @@ bool
|
|||||||
sc_mouse_aoa_init(struct sc_mouse_aoa *mouse, struct sc_aoa *aoa) {
|
sc_mouse_aoa_init(struct sc_mouse_aoa *mouse, struct sc_aoa *aoa) {
|
||||||
mouse->aoa = aoa;
|
mouse->aoa = aoa;
|
||||||
|
|
||||||
bool ok = sc_aoa_setup_hid(aoa, HID_MOUSE_ACCESSORY_ID,
|
struct sc_hid_open hid_open;
|
||||||
SC_HID_MOUSE_REPORT_DESC,
|
sc_hid_mouse_generate_open(&hid_open);
|
||||||
SC_HID_MOUSE_REPORT_DESC_LEN);
|
|
||||||
|
bool ok = sc_aoa_push_open(aoa, &hid_open, true);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGW("Register HID mouse failed");
|
LOGW("Could not push AOA HID open (mouse)");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,8 +78,6 @@ sc_mouse_aoa_init(struct sc_mouse_aoa *mouse, struct sc_aoa *aoa) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
sc_mouse_aoa_destroy(struct sc_mouse_aoa *mouse) {
|
sc_mouse_aoa_destroy(struct sc_mouse_aoa *mouse) {
|
||||||
bool ok = sc_aoa_unregister_hid(mouse->aoa, HID_MOUSE_ACCESSORY_ID);
|
(void) mouse;
|
||||||
if (!ok) {
|
// Do nothing, mouse->aoa will automatically unregister all devices
|
||||||
LOGW("Could not unregister HID mouse");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ struct scrcpy_otg {
|
|||||||
struct sc_aoa aoa;
|
struct sc_aoa aoa;
|
||||||
struct sc_keyboard_aoa keyboard;
|
struct sc_keyboard_aoa keyboard;
|
||||||
struct sc_mouse_aoa mouse;
|
struct sc_mouse_aoa mouse;
|
||||||
|
struct sc_gamepad_aoa gamepad;
|
||||||
|
|
||||||
struct sc_screen_otg screen_otg;
|
struct sc_screen_otg screen_otg;
|
||||||
};
|
};
|
||||||
@ -21,12 +22,7 @@ sc_usb_on_disconnected(struct sc_usb *usb, void *userdata) {
|
|||||||
(void) usb;
|
(void) usb;
|
||||||
(void) userdata;
|
(void) userdata;
|
||||||
|
|
||||||
SDL_Event event;
|
sc_push_event(SC_EVENT_USB_DEVICE_DISCONNECTED);
|
||||||
event.type = SC_EVENT_USB_DEVICE_DISCONNECTED;
|
|
||||||
int ret = SDL_PushEvent(&event);
|
|
||||||
if (ret < 0) {
|
|
||||||
LOGE("Could not post USB disconnection event: %s", SDL_GetError());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static enum scrcpy_exit_code
|
static enum scrcpy_exit_code
|
||||||
@ -37,6 +33,9 @@ event_loop(struct scrcpy_otg *s) {
|
|||||||
case SC_EVENT_USB_DEVICE_DISCONNECTED:
|
case SC_EVENT_USB_DEVICE_DISCONNECTED:
|
||||||
LOGW("Device disconnected");
|
LOGW("Device disconnected");
|
||||||
return SCRCPY_EXIT_DISCONNECTED;
|
return SCRCPY_EXIT_DISCONNECTED;
|
||||||
|
case SC_EVENT_AOA_OPEN_ERROR:
|
||||||
|
LOGE("AOA open error");
|
||||||
|
return SCRCPY_EXIT_FAILURE;
|
||||||
case SDL_QUIT:
|
case SDL_QUIT:
|
||||||
LOGD("User requested to quit");
|
LOGD("User requested to quit");
|
||||||
return SCRCPY_EXIT_SUCCESS;
|
return SCRCPY_EXIT_SUCCESS;
|
||||||
@ -59,12 +58,23 @@ scrcpy_otg(struct scrcpy_options *options) {
|
|||||||
LOGW("Could not enable linear filtering");
|
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
|
// Minimal SDL initialization
|
||||||
if (SDL_Init(SDL_INIT_EVENTS)) {
|
if (SDL_Init(SDL_INIT_EVENTS)) {
|
||||||
LOGE("Could not initialize SDL: %s", SDL_GetError());
|
LOGE("Could not initialize SDL: %s", SDL_GetError());
|
||||||
return SCRCPY_EXIT_FAILURE;
|
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());
|
||||||
|
// Not fatal, keyboard/mouse should still work
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
atexit(SDL_Quit);
|
atexit(SDL_Quit);
|
||||||
|
|
||||||
if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) {
|
if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) {
|
||||||
@ -75,6 +85,7 @@ scrcpy_otg(struct scrcpy_options *options) {
|
|||||||
|
|
||||||
struct sc_keyboard_aoa *keyboard = NULL;
|
struct sc_keyboard_aoa *keyboard = NULL;
|
||||||
struct sc_mouse_aoa *mouse = NULL;
|
struct sc_mouse_aoa *mouse = NULL;
|
||||||
|
struct sc_gamepad_aoa *gamepad = NULL;
|
||||||
bool usb_device_initialized = false;
|
bool usb_device_initialized = false;
|
||||||
bool usb_connected = false;
|
bool usb_connected = false;
|
||||||
bool aoa_started = false;
|
bool aoa_started = false;
|
||||||
@ -121,11 +132,15 @@ scrcpy_otg(struct scrcpy_options *options) {
|
|||||||
|| options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_DISABLED);
|
|| options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_DISABLED);
|
||||||
assert(options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA
|
assert(options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA
|
||||||
|| options->mouse_input_mode == SC_MOUSE_INPUT_MODE_DISABLED);
|
|| options->mouse_input_mode == SC_MOUSE_INPUT_MODE_DISABLED);
|
||||||
|
assert(options->gamepad_input_mode == SC_GAMEPAD_INPUT_MODE_AOA
|
||||||
|
|| options->gamepad_input_mode == SC_GAMEPAD_INPUT_MODE_DISABLED);
|
||||||
|
|
||||||
bool enable_keyboard =
|
bool enable_keyboard =
|
||||||
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA;
|
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA;
|
||||||
bool enable_mouse =
|
bool enable_mouse =
|
||||||
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA;
|
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA;
|
||||||
|
bool enable_gamepad =
|
||||||
|
options->gamepad_input_mode == SC_GAMEPAD_INPUT_MODE_AOA;
|
||||||
|
|
||||||
if (enable_keyboard) {
|
if (enable_keyboard) {
|
||||||
ok = sc_keyboard_aoa_init(&s->keyboard, &s->aoa);
|
ok = sc_keyboard_aoa_init(&s->keyboard, &s->aoa);
|
||||||
@ -143,6 +158,11 @@ scrcpy_otg(struct scrcpy_options *options) {
|
|||||||
mouse = &s->mouse;
|
mouse = &s->mouse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (enable_gamepad) {
|
||||||
|
sc_gamepad_aoa_init(&s->gamepad, &s->aoa);
|
||||||
|
gamepad = &s->gamepad;
|
||||||
|
}
|
||||||
|
|
||||||
ok = sc_aoa_start(&s->aoa);
|
ok = sc_aoa_start(&s->aoa);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
goto end;
|
goto end;
|
||||||
@ -157,6 +177,7 @@ scrcpy_otg(struct scrcpy_options *options) {
|
|||||||
struct sc_screen_otg_params params = {
|
struct sc_screen_otg_params params = {
|
||||||
.keyboard = keyboard,
|
.keyboard = keyboard,
|
||||||
.mouse = mouse,
|
.mouse = mouse,
|
||||||
|
.gamepad = gamepad,
|
||||||
.window_title = window_title,
|
.window_title = window_title,
|
||||||
.always_on_top = options->always_on_top,
|
.always_on_top = options->always_on_top,
|
||||||
.window_x = options->window_x,
|
.window_x = options->window_x,
|
||||||
@ -190,6 +211,9 @@ end:
|
|||||||
if (keyboard) {
|
if (keyboard) {
|
||||||
sc_keyboard_aoa_destroy(&s->keyboard);
|
sc_keyboard_aoa_destroy(&s->keyboard);
|
||||||
}
|
}
|
||||||
|
if (gamepad) {
|
||||||
|
sc_gamepad_aoa_destroy(&s->gamepad);
|
||||||
|
}
|
||||||
|
|
||||||
if (aoa_initialized) {
|
if (aoa_initialized) {
|
||||||
sc_aoa_join(&s->aoa);
|
sc_aoa_join(&s->aoa);
|
||||||
|
@ -59,6 +59,7 @@ sc_screen_otg_init(struct sc_screen_otg *screen,
|
|||||||
const struct sc_screen_otg_params *params) {
|
const struct sc_screen_otg_params *params) {
|
||||||
screen->keyboard = params->keyboard;
|
screen->keyboard = params->keyboard;
|
||||||
screen->mouse = params->mouse;
|
screen->mouse = params->mouse;
|
||||||
|
screen->gamepad = params->gamepad;
|
||||||
|
|
||||||
screen->mouse_capture_key_pressed = 0;
|
screen->mouse_capture_key_pressed = 0;
|
||||||
|
|
||||||
@ -169,7 +170,7 @@ sc_screen_otg_process_mouse_motion(struct sc_screen_otg *screen,
|
|||||||
// .position not used for HID events
|
// .position not used for HID events
|
||||||
.xrel = event->xrel,
|
.xrel = event->xrel,
|
||||||
.yrel = event->yrel,
|
.yrel = event->yrel,
|
||||||
.buttons_state = sc_mouse_buttons_state_from_sdl(event->state, NULL),
|
.buttons_state = sc_mouse_buttons_state_from_sdl(event->state),
|
||||||
};
|
};
|
||||||
|
|
||||||
assert(mp->ops->process_mouse_motion);
|
assert(mp->ops->process_mouse_motion);
|
||||||
@ -188,8 +189,7 @@ sc_screen_otg_process_mouse_button(struct sc_screen_otg *screen,
|
|||||||
// .position not used for HID events
|
// .position not used for HID events
|
||||||
.action = sc_action_from_sdl_mousebutton_type(event->type),
|
.action = sc_action_from_sdl_mousebutton_type(event->type),
|
||||||
.button = sc_mouse_button_from_sdl(event->button),
|
.button = sc_mouse_button_from_sdl(event->button),
|
||||||
.buttons_state =
|
.buttons_state = sc_mouse_buttons_state_from_sdl(sdl_buttons_state),
|
||||||
sc_mouse_buttons_state_from_sdl(sdl_buttons_state, NULL),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
assert(mp->ops->process_mouse_click);
|
assert(mp->ops->process_mouse_click);
|
||||||
@ -208,14 +208,94 @@ sc_screen_otg_process_mouse_wheel(struct sc_screen_otg *screen,
|
|||||||
// .position not used for HID events
|
// .position not used for HID events
|
||||||
.hscroll = event->x,
|
.hscroll = event->x,
|
||||||
.vscroll = event->y,
|
.vscroll = event->y,
|
||||||
.buttons_state =
|
.buttons_state = sc_mouse_buttons_state_from_sdl(sdl_buttons_state),
|
||||||
sc_mouse_buttons_state_from_sdl(sdl_buttons_state, NULL),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
assert(mp->ops->process_mouse_scroll);
|
assert(mp->ops->process_mouse_scroll);
|
||||||
mp->ops->process_mouse_scroll(mp, &evt);
|
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;
|
||||||
|
|
||||||
|
SDL_JoystickID id;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
id = SDL_JoystickInstanceID(joystick);
|
||||||
|
} else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
|
||||||
|
id = event->which;
|
||||||
|
|
||||||
|
SDL_GameController *gc = SDL_GameControllerFromInstanceID(id);
|
||||||
|
if (gc) {
|
||||||
|
SDL_GameControllerClose(gc);
|
||||||
|
} else {
|
||||||
|
LOGW("Unknown gamepad device removed");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Nothing to do
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sc_gamepad_device_event evt = {
|
||||||
|
.type = sc_gamepad_device_event_type_from_sdl_type(event->type),
|
||||||
|
.gamepad_id = id,
|
||||||
|
};
|
||||||
|
gp->ops->process_gamepad_device(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
|
void
|
||||||
sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event) {
|
sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event) {
|
||||||
switch (event->type) {
|
switch (event->type) {
|
||||||
@ -295,5 +375,23 @@ sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event) {
|
|||||||
sc_screen_otg_process_mouse_wheel(screen, &event->wheel);
|
sc_screen_otg_process_mouse_wheel(screen, &event->wheel);
|
||||||
}
|
}
|
||||||
break;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,10 +8,12 @@
|
|||||||
|
|
||||||
#include "keyboard_aoa.h"
|
#include "keyboard_aoa.h"
|
||||||
#include "mouse_aoa.h"
|
#include "mouse_aoa.h"
|
||||||
|
#include "gamepad_aoa.h"
|
||||||
|
|
||||||
struct sc_screen_otg {
|
struct sc_screen_otg {
|
||||||
struct sc_keyboard_aoa *keyboard;
|
struct sc_keyboard_aoa *keyboard;
|
||||||
struct sc_mouse_aoa *mouse;
|
struct sc_mouse_aoa *mouse;
|
||||||
|
struct sc_gamepad_aoa *gamepad;
|
||||||
|
|
||||||
SDL_Window *window;
|
SDL_Window *window;
|
||||||
SDL_Renderer *renderer;
|
SDL_Renderer *renderer;
|
||||||
@ -24,6 +26,7 @@ struct sc_screen_otg {
|
|||||||
struct sc_screen_otg_params {
|
struct sc_screen_otg_params {
|
||||||
struct sc_keyboard_aoa *keyboard;
|
struct sc_keyboard_aoa *keyboard;
|
||||||
struct sc_mouse_aoa *mouse;
|
struct sc_mouse_aoa *mouse;
|
||||||
|
struct sc_gamepad_aoa *gamepad;
|
||||||
|
|
||||||
const char *window_title;
|
const char *window_title;
|
||||||
bool always_on_top;
|
bool always_on_top;
|
||||||
|
@ -13,6 +13,12 @@ sc_write16be(uint8_t *buf, uint16_t value) {
|
|||||||
buf[1] = value;
|
buf[1] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
sc_write16le(uint8_t *buf, uint16_t value) {
|
||||||
|
buf[0] = value;
|
||||||
|
buf[1] = value >> 8;
|
||||||
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
sc_write32be(uint8_t *buf, uint32_t value) {
|
sc_write32be(uint8_t *buf, uint32_t value) {
|
||||||
buf[0] = value >> 24;
|
buf[0] = value >> 24;
|
||||||
@ -21,12 +27,26 @@ sc_write32be(uint8_t *buf, uint32_t value) {
|
|||||||
buf[3] = value;
|
buf[3] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
sc_write32le(uint8_t *buf, uint32_t value) {
|
||||||
|
buf[0] = value;
|
||||||
|
buf[1] = value >> 8;
|
||||||
|
buf[2] = value >> 16;
|
||||||
|
buf[3] = value >> 24;
|
||||||
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
sc_write64be(uint8_t *buf, uint64_t value) {
|
sc_write64be(uint8_t *buf, uint64_t value) {
|
||||||
sc_write32be(buf, value >> 32);
|
sc_write32be(buf, value >> 32);
|
||||||
sc_write32be(&buf[4], (uint32_t) value);
|
sc_write32be(&buf[4], (uint32_t) value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
sc_write64le(uint8_t *buf, uint64_t value) {
|
||||||
|
sc_write32le(buf, (uint32_t) value);
|
||||||
|
sc_write32le(&buf[4], value >> 32);
|
||||||
|
}
|
||||||
|
|
||||||
static inline uint16_t
|
static inline uint16_t
|
||||||
sc_read16be(const uint8_t *buf) {
|
sc_read16be(const uint8_t *buf) {
|
||||||
return (buf[0] << 8) | buf[1];
|
return (buf[0] << 8) | buf[1];
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
# include <sys/types.h>
|
# include <sys/types.h>
|
||||||
# include <sys/socket.h>
|
# include <sys/socket.h>
|
||||||
# include <netinet/in.h>
|
# include <netinet/in.h>
|
||||||
|
# include <netinet/tcp.h>
|
||||||
# include <arpa/inet.h>
|
# include <arpa/inet.h>
|
||||||
# include <unistd.h>
|
# include <unistd.h>
|
||||||
# include <fcntl.h>
|
# include <fcntl.h>
|
||||||
@ -273,6 +274,22 @@ net_close(sc_socket socket) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
net_set_tcp_nodelay(sc_socket socket, bool tcp_nodelay) {
|
||||||
|
sc_raw_socket raw_sock = unwrap(socket);
|
||||||
|
|
||||||
|
int value = tcp_nodelay ? 1 : 0;
|
||||||
|
int ret = setsockopt(raw_sock, IPPROTO_TCP, TCP_NODELAY,
|
||||||
|
(const void *) &value, sizeof(value));
|
||||||
|
if (ret == -1) {
|
||||||
|
net_perror("setsockopt(TCP_NODELAY)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(ret == 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
net_parse_ipv4(const char *s, uint32_t *ipv4) {
|
net_parse_ipv4(const char *s, uint32_t *ipv4) {
|
||||||
struct in_addr addr;
|
struct in_addr addr;
|
||||||
|
@ -67,6 +67,10 @@ net_interrupt(sc_socket socket);
|
|||||||
bool
|
bool
|
||||||
net_close(sc_socket socket);
|
net_close(sc_socket socket);
|
||||||
|
|
||||||
|
// Disable Nagle's algorithm (if tcp_nodelay is true)
|
||||||
|
bool
|
||||||
|
net_set_tcp_nodelay(sc_socket socket, bool tcp_nodelay);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse `ip` "xxx.xxx.xxx.xxx" to an IPv4 host representation
|
* Parse `ip` "xxx.xxx.xxx.xxx" to an IPv4 host representation
|
||||||
*/
|
*/
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
|
sc_thread_id SC_MAIN_THREAD_ID;
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name,
|
sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name,
|
||||||
void *userdata) {
|
void *userdata) {
|
||||||
|
@ -39,6 +39,8 @@ typedef struct sc_cond {
|
|||||||
SDL_cond *cond;
|
SDL_cond *cond;
|
||||||
} sc_cond;
|
} sc_cond;
|
||||||
|
|
||||||
|
extern sc_thread_id SC_MAIN_THREAD_ID;
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name,
|
sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name,
|
||||||
void *userdata);
|
void *userdata);
|
||||||
|
@ -240,7 +240,7 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs, const AVCodecContext *ctx) {
|
|||||||
vs->frame = av_frame_alloc();
|
vs->frame = av_frame_alloc();
|
||||||
if (!vs->frame) {
|
if (!vs->frame) {
|
||||||
LOG_OOM();
|
LOG_OOM();
|
||||||
goto error_avcodec_close;
|
goto error_avcodec_free_context;
|
||||||
}
|
}
|
||||||
|
|
||||||
vs->packet = av_packet_alloc();
|
vs->packet = av_packet_alloc();
|
||||||
@ -268,8 +268,6 @@ error_av_packet_free:
|
|||||||
av_packet_free(&vs->packet);
|
av_packet_free(&vs->packet);
|
||||||
error_av_frame_free:
|
error_av_frame_free:
|
||||||
av_frame_free(&vs->frame);
|
av_frame_free(&vs->frame);
|
||||||
error_avcodec_close:
|
|
||||||
avcodec_close(vs->encoder_ctx);
|
|
||||||
error_avcodec_free_context:
|
error_avcodec_free_context:
|
||||||
avcodec_free_context(&vs->encoder_ctx);
|
avcodec_free_context(&vs->encoder_ctx);
|
||||||
error_avio_close:
|
error_avio_close:
|
||||||
@ -297,7 +295,6 @@ sc_v4l2_sink_close(struct sc_v4l2_sink *vs) {
|
|||||||
|
|
||||||
av_packet_free(&vs->packet);
|
av_packet_free(&vs->packet);
|
||||||
av_frame_free(&vs->frame);
|
av_frame_free(&vs->frame);
|
||||||
avcodec_close(vs->encoder_ctx);
|
|
||||||
avcodec_free_context(&vs->encoder_ctx);
|
avcodec_free_context(&vs->encoder_ctx);
|
||||||
avio_close(vs->format_ctx->pb);
|
avio_close(vs->format_ctx->pb);
|
||||||
avformat_free_context(vs->format_ctx);
|
avformat_free_context(vs->format_ctx);
|
||||||
|
@ -42,6 +42,44 @@ static void test_write64be(void) {
|
|||||||
assert(buf[7] == 0xEF);
|
assert(buf[7] == 0xEF);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_write16le(void) {
|
||||||
|
uint16_t val = 0xABCD;
|
||||||
|
uint8_t buf[2];
|
||||||
|
|
||||||
|
sc_write16le(buf, val);
|
||||||
|
|
||||||
|
assert(buf[0] == 0xCD);
|
||||||
|
assert(buf[1] == 0xAB);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_write32le(void) {
|
||||||
|
uint32_t val = 0xABCD1234;
|
||||||
|
uint8_t buf[4];
|
||||||
|
|
||||||
|
sc_write32le(buf, val);
|
||||||
|
|
||||||
|
assert(buf[0] == 0x34);
|
||||||
|
assert(buf[1] == 0x12);
|
||||||
|
assert(buf[2] == 0xCD);
|
||||||
|
assert(buf[3] == 0xAB);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_write64le(void) {
|
||||||
|
uint64_t val = 0xABCD1234567890EF;
|
||||||
|
uint8_t buf[8];
|
||||||
|
|
||||||
|
sc_write64le(buf, val);
|
||||||
|
|
||||||
|
assert(buf[0] == 0xEF);
|
||||||
|
assert(buf[1] == 0x90);
|
||||||
|
assert(buf[2] == 0x78);
|
||||||
|
assert(buf[3] == 0x56);
|
||||||
|
assert(buf[4] == 0x34);
|
||||||
|
assert(buf[5] == 0x12);
|
||||||
|
assert(buf[6] == 0xCD);
|
||||||
|
assert(buf[7] == 0xAB);
|
||||||
|
}
|
||||||
|
|
||||||
static void test_read16be(void) {
|
static void test_read16be(void) {
|
||||||
uint8_t buf[2] = {0xAB, 0xCD};
|
uint8_t buf[2] = {0xAB, 0xCD};
|
||||||
|
|
||||||
@ -108,6 +146,10 @@ int main(int argc, char *argv[]) {
|
|||||||
test_read32be();
|
test_read32be();
|
||||||
test_read64be();
|
test_read64be();
|
||||||
|
|
||||||
|
test_write16le();
|
||||||
|
test_write32le();
|
||||||
|
test_write64le();
|
||||||
|
|
||||||
test_float_to_u16fp();
|
test_float_to_u16fp();
|
||||||
test_float_to_i16fp();
|
test_float_to_i16fp();
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -78,7 +78,7 @@ static void test_options(void) {
|
|||||||
assert(opts->video_bit_rate == 5000000);
|
assert(opts->video_bit_rate == 5000000);
|
||||||
assert(!strcmp(opts->crop, "100:200:300:400"));
|
assert(!strcmp(opts->crop, "100:200:300:400"));
|
||||||
assert(opts->fullscreen);
|
assert(opts->fullscreen);
|
||||||
assert(opts->max_fps == 30);
|
assert(!strcmp(opts->max_fps, "30"));
|
||||||
assert(opts->max_size == 1024);
|
assert(opts->max_size == 1024);
|
||||||
assert(opts->lock_video_orientation == 2);
|
assert(opts->lock_video_orientation == 2);
|
||||||
assert(opts->port_range.first == 1234);
|
assert(opts->port_range.first == 1234);
|
||||||
|
@ -329,6 +329,7 @@ static void test_serialize_uhid_create(void) {
|
|||||||
.type = SC_CONTROL_MSG_TYPE_UHID_CREATE,
|
.type = SC_CONTROL_MSG_TYPE_UHID_CREATE,
|
||||||
.uhid_create = {
|
.uhid_create = {
|
||||||
.id = 42,
|
.id = 42,
|
||||||
|
.name = "ABC",
|
||||||
.report_desc_size = sizeof(report_desc),
|
.report_desc_size = sizeof(report_desc),
|
||||||
.report_desc = report_desc,
|
.report_desc = report_desc,
|
||||||
},
|
},
|
||||||
@ -336,12 +337,14 @@ static void test_serialize_uhid_create(void) {
|
|||||||
|
|
||||||
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
|
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
|
||||||
size_t size = sc_control_msg_serialize(&msg, buf);
|
size_t size = sc_control_msg_serialize(&msg, buf);
|
||||||
assert(size == 16);
|
assert(size == 20);
|
||||||
|
|
||||||
const uint8_t expected[] = {
|
const uint8_t expected[] = {
|
||||||
SC_CONTROL_MSG_TYPE_UHID_CREATE,
|
SC_CONTROL_MSG_TYPE_UHID_CREATE,
|
||||||
0, 42, // id
|
0, 42, // id
|
||||||
0, 11, // size
|
3, // name size
|
||||||
|
65, 66, 67, // "ABC"
|
||||||
|
0, 11, // report desc size
|
||||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
|
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
|
||||||
};
|
};
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
@ -370,6 +373,25 @@ static void test_serialize_uhid_input(void) {
|
|||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_serialize_uhid_destroy(void) {
|
||||||
|
struct sc_control_msg msg = {
|
||||||
|
.type = SC_CONTROL_MSG_TYPE_UHID_DESTROY,
|
||||||
|
.uhid_destroy = {
|
||||||
|
.id = 42,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
|
||||||
|
size_t size = sc_control_msg_serialize(&msg, buf);
|
||||||
|
assert(size == 3);
|
||||||
|
|
||||||
|
const uint8_t expected[] = {
|
||||||
|
SC_CONTROL_MSG_TYPE_UHID_DESTROY,
|
||||||
|
0, 42, // id
|
||||||
|
};
|
||||||
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
|
}
|
||||||
|
|
||||||
static void test_serialize_open_hard_keyboard(void) {
|
static void test_serialize_open_hard_keyboard(void) {
|
||||||
struct sc_control_msg msg = {
|
struct sc_control_msg msg = {
|
||||||
.type = SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS,
|
.type = SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS,
|
||||||
@ -405,6 +427,7 @@ int main(int argc, char *argv[]) {
|
|||||||
test_serialize_rotate_device();
|
test_serialize_rotate_device();
|
||||||
test_serialize_uhid_create();
|
test_serialize_uhid_create();
|
||||||
test_serialize_uhid_input();
|
test_serialize_uhid_input();
|
||||||
|
test_serialize_uhid_destroy();
|
||||||
test_serialize_open_hard_keyboard();
|
test_serialize_open_hard_keyboard();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
26
doc/audio.md
26
doc/audio.md
@ -42,7 +42,7 @@ scrcpy --no-window
|
|||||||
# interrupt with Ctrl+C
|
# interrupt with Ctrl+C
|
||||||
```
|
```
|
||||||
|
|
||||||
Without video, the audio latency is typically not criticial, so it might be
|
Without video, the audio latency is typically not critical, so it might be
|
||||||
interesting to add [buffering](#buffering) to minimize glitches:
|
interesting to add [buffering](#buffering) to minimize glitches:
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -66,6 +66,30 @@ the computer:
|
|||||||
scrcpy --audio-source=mic --no-video --no-playback --record=file.opus
|
scrcpy --audio-source=mic --no-video --no-playback --record=file.opus
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Duplication
|
||||||
|
|
||||||
|
An alternative device audio capture method is also available (only for Android
|
||||||
|
13 and above):
|
||||||
|
|
||||||
|
```
|
||||||
|
scrcpy --audio-source=playback
|
||||||
|
```
|
||||||
|
|
||||||
|
This audio source supports keeping the audio playing on the device while
|
||||||
|
mirroring, with `--audio-dup`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --audio-source=playback --audio-dup
|
||||||
|
# or simply:
|
||||||
|
scrcpy --audio-dup # --audio-source=playback is implied
|
||||||
|
```
|
||||||
|
|
||||||
|
However, it requires Android 13, and Android apps can opt-out (so they are not
|
||||||
|
captured).
|
||||||
|
|
||||||
|
|
||||||
|
See [#4380](https://github.com/Genymobile/scrcpy/issues/4380).
|
||||||
|
|
||||||
|
|
||||||
## Codec
|
## Codec
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ This is the preferred method (and the way the release is built).
|
|||||||
From _Debian_, install _mingw_:
|
From _Debian_, install _mingw_:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt install mingw-w64 mingw-w64-tools
|
sudo apt install mingw-w64 mingw-w64-tools libz-mingw-w64-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
You also need the JDK to build the server:
|
You also need the JDK to build the server:
|
||||||
@ -233,10 +233,10 @@ install` must be run as root)._
|
|||||||
|
|
||||||
#### Option 2: Use prebuilt server
|
#### Option 2: Use prebuilt server
|
||||||
|
|
||||||
- [`scrcpy-server-v2.4`][direct-scrcpy-server]
|
- [`scrcpy-server-v2.7`][direct-scrcpy-server]
|
||||||
<sub>SHA-256: `93c272b7438605c055e127f7444064ed78fa9ca49f81156777fd201e79ce7ba3`</sub>
|
<sub>SHA-256: `a23c5659f36c260f105c022d27bcb3eafffa26070e7baa9eda66d01377a1adba`</sub>
|
||||||
|
|
||||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.4/scrcpy-server-v2.4
|
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.7/scrcpy-server-v2.7
|
||||||
|
|
||||||
Download the prebuilt server somewhere, and specify its path during the Meson
|
Download the prebuilt server somewhere, and specify its path during the Meson
|
||||||
configuration:
|
configuration:
|
||||||
|
58
doc/gamepad.md
Normal file
58
doc/gamepad.md
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# Gamepad
|
||||||
|
|
||||||
|
Several gamepad input modes are available:
|
||||||
|
|
||||||
|
- `--gamepad=disabled` (default)
|
||||||
|
- `--gamepad=uhid` (or `-G`): simulates physical HID gamepads using the UHID
|
||||||
|
kernel module on the device
|
||||||
|
- `--gamepad=aoa`: simulates physical HID gamepads using the AOAv2 protocol
|
||||||
|
|
||||||
|
|
||||||
|
## Physical gamepad simulation
|
||||||
|
|
||||||
|
Two modes allow to simulate physical HID gamepads on the device, one for each
|
||||||
|
physical gamepad plugged into the computer.
|
||||||
|
|
||||||
|
|
||||||
|
### UHID
|
||||||
|
|
||||||
|
This mode simulates physical HID gamepads using the [UHID] kernel module on the
|
||||||
|
device.
|
||||||
|
|
||||||
|
[UHID]: https://kernel.org/doc/Documentation/hid/uhid.txt
|
||||||
|
|
||||||
|
To enable UHID gamepads, use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --gamepad=uhid
|
||||||
|
scrcpy -G # short version
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: UHID may not work on old Android versions due to permission errors.
|
||||||
|
|
||||||
|
|
||||||
|
### AOA
|
||||||
|
|
||||||
|
This mode simulates physical HID gamepads using the [AOAv2] protocol.
|
||||||
|
|
||||||
|
[AOAv2]: https://source.android.com/devices/accessories/aoa2#hid-support
|
||||||
|
|
||||||
|
To enable AOA gamepads, use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --gamepad=aoa
|
||||||
|
```
|
||||||
|
|
||||||
|
Contrary to the other mode, it works at the USB level directly (so it only works
|
||||||
|
over USB).
|
||||||
|
|
||||||
|
It does not use the scrcpy server, and does not require `adb` (USB debugging).
|
||||||
|
Therefore, it is possible to control the device (but not mirror) even with USB
|
||||||
|
debugging disabled (see [OTG](otg.md)).
|
||||||
|
|
||||||
|
Note: For some reason, in this mode, Android detects multiple physical gamepads
|
||||||
|
as a single misbehaving one. Use UHID if you need multiple gamepads.
|
||||||
|
|
||||||
|
Note: On Windows, it may only work in [OTG mode](otg.md), not while mirroring
|
||||||
|
(it is not possible to open a USB device if it is already open by another
|
||||||
|
process like the _adb daemon_).
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
Scrcpy is packaged in several distributions and package managers:
|
Scrcpy is packaged in several distributions and package managers:
|
||||||
|
|
||||||
- Debian/Ubuntu: `apt install scrcpy`
|
- Debian/Ubuntu: ~~`apt install scrcpy`~~ _(obsolete version)_
|
||||||
- Arch Linux: `pacman -S scrcpy`
|
- Arch Linux: `pacman -S scrcpy`
|
||||||
- Fedora: `dnf copr enable zeno/scrcpy && dnf install scrcpy`
|
- Fedora: `dnf copr enable zeno/scrcpy && dnf install scrcpy`
|
||||||
- Gentoo: `emerge scrcpy`
|
- Gentoo: `emerge scrcpy`
|
||||||
|
62
doc/mouse.md
62
doc/mouse.md
@ -18,6 +18,14 @@ Note that on some devices, an additional option must be enabled in developer
|
|||||||
options for this mouse mode to work. See
|
options for this mouse mode to work. See
|
||||||
[prerequisites](/README.md#prerequisites).
|
[prerequisites](/README.md#prerequisites).
|
||||||
|
|
||||||
|
### Mouse hover
|
||||||
|
|
||||||
|
By default, mouse hover (mouse motion without any clicks) events are forwarded
|
||||||
|
to the device. This can be disabled with:
|
||||||
|
|
||||||
|
```
|
||||||
|
scrcpy --no-mouse-hover
|
||||||
|
```
|
||||||
|
|
||||||
## Physical mouse simulation
|
## Physical mouse simulation
|
||||||
|
|
||||||
@ -45,6 +53,8 @@ scrcpy --mouse=uhid
|
|||||||
scrcpy -M # short version
|
scrcpy -M # short version
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note: UHID may not work on old Android versions due to permission errors.
|
||||||
|
|
||||||
|
|
||||||
### AOA
|
### AOA
|
||||||
|
|
||||||
@ -72,21 +82,37 @@ process like the _adb daemon_).
|
|||||||
|
|
||||||
## Mouse bindings
|
## Mouse bindings
|
||||||
|
|
||||||
By default, with SDK mouse, right-click triggers BACK (or POWER on) and
|
By default, with SDK mouse:
|
||||||
middle-click triggers HOME. In addition, the 4th click triggers APP_SWITCH and
|
- right-click triggers BACK (or POWER on)
|
||||||
the 5th click expands the notification panel.
|
- middle-click triggers HOME
|
||||||
|
- the 4th click triggers APP_SWITCH
|
||||||
|
- the 5th click expands the notification panel
|
||||||
|
|
||||||
In AOA and UHID mouse modes, all clicks are forwarded by default.
|
The secondary clicks may be forwarded to the device instead by pressing the
|
||||||
|
<kbd>Shift</kbd> key (e.g. <kbd>Shift</kbd>+right-click injects a right click to
|
||||||
|
the device).
|
||||||
|
|
||||||
The shortcuts can be configured using `--mouse-bind=xxxx` for any mouse mode.
|
In AOA and UHID mouse modes, the default bindings are reversed: all clicks are
|
||||||
The argument must be exactly 4 characters, one for each secondary click:
|
forwarded by default, and pressing <kbd>Shift</kbd> gives access to the
|
||||||
|
shortcuts (since the cursor is handled on the device side, it makes more sense
|
||||||
|
to forward all mouse buttons by default in these modes).
|
||||||
|
|
||||||
|
The shortcuts can be configured using `--mouse-bind=xxxx:xxxx` for any mouse
|
||||||
|
mode. The argument must be one or two sequences (separated by `:`) of exactly 4
|
||||||
|
characters, one for each secondary click:
|
||||||
|
|
||||||
```
|
```
|
||||||
--mouse-bind=xxxx
|
.---- Shift + right click
|
||||||
|
SECONDARY |.--- Shift + middle click
|
||||||
|
BINDINGS ||.-- Shift + 4th click
|
||||||
|
|||.- Shift + 5th click
|
||||||
|
||||
|
||||||
|
vvvv
|
||||||
|
--mouse-bind=xxxx:xxxx
|
||||||
^^^^
|
^^^^
|
||||||
||||
|
||||
|
||||||
||| `- 5th click
|
PRIMARY ||| `- 5th click
|
||||||
|| `-- 4th click
|
BINDINGS || `-- 4th click
|
||||||
| `--- middle click
|
| `--- middle click
|
||||||
`---- right click
|
`---- right click
|
||||||
```
|
```
|
||||||
@ -103,8 +129,18 @@ Each character must be one of the following:
|
|||||||
For example:
|
For example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --mouse-bind=bhsn # the default mode with SDK mouse
|
scrcpy --mouse-bind=bhsn:++++ # the default mode for SDK mouse
|
||||||
scrcpy --mouse-bind=++++ # forward all clicks (default for AOA/UHID)
|
scrcpy --mouse-bind=++++:bhsn # the default mode for AOA and UHID
|
||||||
scrcpy --mouse-bind=++bh # forward right and middle clicks,
|
scrcpy --mouse-bind=++bh:++sn # forward right and middle clicks,
|
||||||
# use 4th and 5th for BACK and HOME
|
# use 4th and 5th for BACK and HOME,
|
||||||
|
# use Shift+4th and Shift+5th for APP_SWITCH
|
||||||
|
# and expand notification panel
|
||||||
|
```
|
||||||
|
|
||||||
|
The second sequence of bindings may be omitted. In that case, it is the same as
|
||||||
|
the first one:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --mouse-bind=bhsn
|
||||||
|
scrcpy --mouse-bind=bhsn:bhsn # equivalent
|
||||||
```
|
```
|
||||||
|
29
doc/otg.md
29
doc/otg.md
@ -6,16 +6,18 @@ was a [physical keyboard] and/or a [physical mouse] connected to the Android
|
|||||||
device (see [keyboard](keyboard.md) and [mouse](mouse.md)).
|
device (see [keyboard](keyboard.md) and [mouse](mouse.md)).
|
||||||
|
|
||||||
[physical keyboard]: keyboard.md#physical-keyboard-simulation
|
[physical keyboard]: keyboard.md#physical-keyboard-simulation
|
||||||
[physical mouse]: physical-keyboard-simulation
|
[physical mouse]: mouse.md#physical-mouse-simulation
|
||||||
|
|
||||||
A special mode (OTG) allows to control the device using AOA
|
A special mode (OTG) allows to control the device using AOA
|
||||||
[keyboard](keyboard.md#aoa) and [mouse](mouse.md#aoa), without using _adb_ at
|
[keyboard](keyboard.md#aoa), [mouse](mouse.md#aoa) and
|
||||||
all (so USB debugging is not necessary). In this mode, video and audio are
|
[gamepad](gamepad.md#aoa), without using _adb_ at all (so USB debugging is not
|
||||||
disabled, and `--keyboard=aoa and `--mouse=aoa` are implicitly set.
|
necessary). In this mode, video and audio are disabled, and `--keyboard=aoa` and
|
||||||
|
`--mouse=aoa` are implicitly set. However, gamepads are disabled by default, so
|
||||||
|
`--gamepad=aoa` (or `-G` in OTG mode) must be explicitly set.
|
||||||
|
|
||||||
Therefore, it is possible to run _scrcpy_ with only physical keyboard and mouse
|
Therefore, it is possible to run _scrcpy_ with only physical keyboard, mouse and
|
||||||
simulation, as if the computer keyboard and mouse were plugged directly to the
|
gamepad simulation, as if the computer keyboard, mouse and gamepads were plugged
|
||||||
device via an OTG cable.
|
directly to the device via an OTG cable.
|
||||||
|
|
||||||
To enable OTG mode:
|
To enable OTG mode:
|
||||||
|
|
||||||
@ -32,6 +34,13 @@ scrcpy --otg --keyboard=disabled
|
|||||||
scrcpy --otg --mouse=disabled
|
scrcpy --otg --mouse=disabled
|
||||||
```
|
```
|
||||||
|
|
||||||
|
and to enable gamepads:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --otg --gamepad=aoa
|
||||||
|
scrcpy --otg -G # short version
|
||||||
|
```
|
||||||
|
|
||||||
It only works if the device is connected over USB.
|
It only works if the device is connected over USB.
|
||||||
|
|
||||||
## OTG issues on Windows
|
## OTG issues on Windows
|
||||||
@ -50,9 +59,9 @@ is enabled, then OTG mode is not necessary.
|
|||||||
Instead, disable video and audio, and select UHID (or AOA):
|
Instead, disable video and audio, and select UHID (or AOA):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --no-video --no-audio --keyboard=uhid --mouse=uhid
|
scrcpy --no-video --no-audio --keyboard=uhid --mouse=uhid --gamepad=uhid
|
||||||
scrcpy --no-video --no-audio -KM # short version
|
scrcpy --no-video --no-audio -KMG # short version
|
||||||
scrcpy --no-video --no-audio --keyboard=aoa --mouse=aoa
|
scrcpy --no-video --no-audio --keyboard=aoa --mouse=aoa --gamepad=aoa
|
||||||
```
|
```
|
||||||
|
|
||||||
One benefit of UHID is that it also works wirelessly.
|
One benefit of UHID is that it also works wirelessly.
|
||||||
|
@ -4,24 +4,18 @@
|
|||||||
|
|
||||||
Download the [latest release]:
|
Download the [latest release]:
|
||||||
|
|
||||||
- [`scrcpy-win64-v2.4.zip`][direct-win64] (64-bit)
|
- [`scrcpy-win64-v2.7.zip`][direct-win64] (64-bit)
|
||||||
<sub>SHA-256: `9dc56f21bfa455352ec0c58b40feaf2fb02d67372910a4235e298ece286ff3a9`</sub>
|
<sub>SHA-256: `5910bc18d5a16f42d84185ddc7e16a4cee6a6f5f33451559c1a1d6d0099bd5f5`</sub>
|
||||||
- [`scrcpy-win32-v2.4.zip`][direct-win32] (32-bit)
|
- [`scrcpy-win32-v2.7.zip`][direct-win32] (32-bit)
|
||||||
<sub>SHA-256: `cf92acc45eef37c6ee2db819f92e420ced3bc50f1348dd57f7d6ca1fc80f6116`</sub>
|
<sub>SHA-256: `ef4daf89d500f33d78b830625536ecb18481429dd94433e7634c824292059d06`</sub>
|
||||||
|
|
||||||
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
|
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
|
||||||
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.4/scrcpy-win64-v2.4.zip
|
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.7/scrcpy-win64-v2.7.zip
|
||||||
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.4/scrcpy-win32-v2.4.zip
|
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.7/scrcpy-win32-v2.7.zip
|
||||||
|
|
||||||
and extract it.
|
and extract it.
|
||||||
|
|
||||||
Alternatively, you could install it from packages manager, like [Winget]:
|
Alternatively, you could install it from packages manager, like [Chocolatey]:
|
||||||
|
|
||||||
```bash
|
|
||||||
winget install scrcpy
|
|
||||||
```
|
|
||||||
|
|
||||||
or [Chocolatey]:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
choco install scrcpy
|
choco install scrcpy
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
BUILDDIR=build-auto
|
BUILDDIR=build-auto
|
||||||
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.4/scrcpy-server-v2.4
|
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.7/scrcpy-server-v2.7
|
||||||
PREBUILT_SERVER_SHA256=93c272b7438605c055e127f7444064ed78fa9ca49f81156777fd201e79ce7ba3
|
PREBUILT_SERVER_SHA256=a23c5659f36c260f105c022d27bcb3eafffa26070e7baa9eda66d01377a1adba
|
||||||
|
|
||||||
echo "[scrcpy] Downloading prebuilt server..."
|
echo "[scrcpy] Downloading prebuilt server..."
|
||||||
wget "$PREBUILT_SERVER_URL" -O scrcpy-server
|
wget "$PREBUILT_SERVER_URL" -O scrcpy-server
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
project('scrcpy', 'c',
|
project('scrcpy', 'c',
|
||||||
version: '2.4',
|
version: '2.7',
|
||||||
meson_version: '>= 0.48',
|
meson_version: '>= 0.48',
|
||||||
default_options: [
|
default_options: [
|
||||||
'c_std=c11',
|
'c_std=c11',
|
||||||
|
@ -24,7 +24,7 @@ SERVER_BUILD_DIR := build-server
|
|||||||
WIN32_BUILD_DIR := build-win32
|
WIN32_BUILD_DIR := build-win32
|
||||||
WIN64_BUILD_DIR := build-win64
|
WIN64_BUILD_DIR := build-win64
|
||||||
|
|
||||||
VERSION := $(shell git describe --tags --always)
|
VERSION ?= $(shell git describe --tags --exclude='*install-release' --always)
|
||||||
|
|
||||||
DIST := dist
|
DIST := dist
|
||||||
WIN32_TARGET_DIR := scrcpy-win32-$(VERSION)
|
WIN32_TARGET_DIR := scrcpy-win32-$(VERSION)
|
||||||
|
@ -7,8 +7,8 @@ android {
|
|||||||
applicationId "com.genymobile.scrcpy"
|
applicationId "com.genymobile.scrcpy"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 34
|
targetSdkVersion 34
|
||||||
versionCode 20400
|
versionCode 20700
|
||||||
versionName "2.4"
|
versionName "2.7"
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
SCRCPY_DEBUG=false
|
SCRCPY_DEBUG=false
|
||||||
SCRCPY_VERSION_NAME=2.4
|
SCRCPY_VERSION_NAME=2.7
|
||||||
|
|
||||||
PLATFORM=${ANDROID_PLATFORM:-34}
|
PLATFORM=${ANDROID_PLATFORM:-34}
|
||||||
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-34.0.0}
|
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-34.0.0}
|
||||||
@ -50,14 +50,29 @@ cd "$SERVER_DIR/src/main/aidl"
|
|||||||
android/content/IOnPrimaryClipChangedListener.aidl
|
android/content/IOnPrimaryClipChangedListener.aidl
|
||||||
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" android/view/IDisplayFoldListener.aidl
|
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" android/view/IDisplayFoldListener.aidl
|
||||||
|
|
||||||
|
SRC=( \
|
||||||
|
com/genymobile/scrcpy/*.java \
|
||||||
|
com/genymobile/scrcpy/audio/*.java \
|
||||||
|
com/genymobile/scrcpy/control/*.java \
|
||||||
|
com/genymobile/scrcpy/device/*.java \
|
||||||
|
com/genymobile/scrcpy/util/*.java \
|
||||||
|
com/genymobile/scrcpy/video/*.java \
|
||||||
|
com/genymobile/scrcpy/wrappers/*.java \
|
||||||
|
)
|
||||||
|
|
||||||
|
CLASSES=()
|
||||||
|
for src in "${SRC[@]}"
|
||||||
|
do
|
||||||
|
CLASSES+=("${src%.java}.class")
|
||||||
|
done
|
||||||
|
|
||||||
echo "Compiling java sources..."
|
echo "Compiling java sources..."
|
||||||
cd ../java
|
cd ../java
|
||||||
javac -bootclasspath "$ANDROID_JAR" \
|
javac -bootclasspath "$ANDROID_JAR" \
|
||||||
-cp "$LAMBDA_JAR:$GEN_DIR" \
|
-cp "$LAMBDA_JAR:$GEN_DIR" \
|
||||||
-d "$CLASSES_DIR" \
|
-d "$CLASSES_DIR" \
|
||||||
-source 1.8 -target 1.8 \
|
-source 1.8 -target 1.8 \
|
||||||
com/genymobile/scrcpy/*.java \
|
${SRC[@]}
|
||||||
com/genymobile/scrcpy/wrappers/*.java
|
|
||||||
|
|
||||||
echo "Dexing..."
|
echo "Dexing..."
|
||||||
cd "$CLASSES_DIR"
|
cd "$CLASSES_DIR"
|
||||||
@ -68,8 +83,7 @@ then
|
|||||||
"$BUILD_TOOLS_DIR/dx" --dex --output "$BUILD_DIR/classes.dex" \
|
"$BUILD_TOOLS_DIR/dx" --dex --output "$BUILD_DIR/classes.dex" \
|
||||||
android/view/*.class \
|
android/view/*.class \
|
||||||
android/content/*.class \
|
android/content/*.class \
|
||||||
com/genymobile/scrcpy/*.class \
|
${CLASSES[@]}
|
||||||
com/genymobile/scrcpy/wrappers/*.class
|
|
||||||
|
|
||||||
echo "Archiving..."
|
echo "Archiving..."
|
||||||
cd "$BUILD_DIR"
|
cd "$BUILD_DIR"
|
||||||
@ -81,8 +95,7 @@ else
|
|||||||
--output "$BUILD_DIR/classes.zip" \
|
--output "$BUILD_DIR/classes.zip" \
|
||||||
android/view/*.class \
|
android/view/*.class \
|
||||||
android/content/*.class \
|
android/content/*.class \
|
||||||
com/genymobile/scrcpy/*.class \
|
${CLASSES[@]}
|
||||||
com/genymobile/scrcpy/wrappers/*.class
|
|
||||||
|
|
||||||
cd "$BUILD_DIR"
|
cd "$BUILD_DIR"
|
||||||
mv classes.zip "$SERVER_BINARY"
|
mv classes.zip "$SERVER_BINARY"
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
package com.genymobile.scrcpy;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exception thrown if audio capture failed on Android 11 specifically because the running App (shell) was not in foreground.
|
|
||||||
*/
|
|
||||||
public class AudioCaptureForegroundException extends Exception {
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
package com.genymobile.scrcpy;
|
|
||||||
|
|
||||||
import android.media.MediaRecorder;
|
|
||||||
|
|
||||||
public enum AudioSource {
|
|
||||||
OUTPUT("output", MediaRecorder.AudioSource.REMOTE_SUBMIX),
|
|
||||||
MIC("mic", MediaRecorder.AudioSource.MIC);
|
|
||||||
|
|
||||||
private final String name;
|
|
||||||
private final int value;
|
|
||||||
|
|
||||||
AudioSource(String name, int value) {
|
|
||||||
this.name = name;
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
int value() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
static AudioSource findByName(String name) {
|
|
||||||
for (AudioSource audioSource : AudioSource.values()) {
|
|
||||||
if (name.equals(audioSource.name)) {
|
|
||||||
return audioSource;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,10 @@
|
|||||||
package com.genymobile.scrcpy;
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
import com.genymobile.scrcpy.device.Device;
|
||||||
|
import com.genymobile.scrcpy.util.Ln;
|
||||||
|
import com.genymobile.scrcpy.util.Settings;
|
||||||
|
import com.genymobile.scrcpy.util.SettingsException;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
package com.genymobile.scrcpy;
|
|
||||||
|
|
||||||
public interface Codec {
|
|
||||||
|
|
||||||
enum Type {
|
|
||||||
VIDEO,
|
|
||||||
AUDIO,
|
|
||||||
}
|
|
||||||
|
|
||||||
Type getType();
|
|
||||||
|
|
||||||
int getId();
|
|
||||||
|
|
||||||
String getName();
|
|
||||||
|
|
||||||
String getMimeType();
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package com.genymobile.scrcpy;
|
|
||||||
|
|
||||||
import android.net.LocalSocket;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
public final class ControlChannel {
|
|
||||||
private final InputStream inputStream;
|
|
||||||
private final OutputStream outputStream;
|
|
||||||
|
|
||||||
private final ControlMessageReader reader = new ControlMessageReader();
|
|
||||||
private final DeviceMessageWriter writer = new DeviceMessageWriter();
|
|
||||||
|
|
||||||
public ControlChannel(LocalSocket controlSocket) throws IOException {
|
|
||||||
this.inputStream = controlSocket.getInputStream();
|
|
||||||
this.outputStream = controlSocket.getOutputStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ControlMessage recv() throws IOException {
|
|
||||||
ControlMessage msg = reader.next();
|
|
||||||
while (msg == null) {
|
|
||||||
reader.readFrom(inputStream);
|
|
||||||
msg = reader.next();
|
|
||||||
}
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void send(DeviceMessage msg) throws IOException {
|
|
||||||
writer.writeTo(msg, outputStream);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,255 +0,0 @@
|
|||||||
package com.genymobile.scrcpy;
|
|
||||||
|
|
||||||
import java.io.EOFException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
public class ControlMessageReader {
|
|
||||||
|
|
||||||
static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13;
|
|
||||||
static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 31;
|
|
||||||
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
|
||||||
static final int BACK_OR_SCREEN_ON_LENGTH = 1;
|
|
||||||
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
|
|
||||||
static final int GET_CLIPBOARD_LENGTH = 1;
|
|
||||||
static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 9;
|
|
||||||
static final int UHID_CREATE_FIXED_PAYLOAD_LENGTH = 4;
|
|
||||||
static final int UHID_INPUT_FIXED_PAYLOAD_LENGTH = 4;
|
|
||||||
|
|
||||||
private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k
|
|
||||||
|
|
||||||
public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 14; // type: 1 byte; sequence: 8 bytes; paste flag: 1 byte; length: 4 bytes
|
|
||||||
public static final int INJECT_TEXT_MAX_LENGTH = 300;
|
|
||||||
|
|
||||||
private final byte[] rawBuffer = new byte[MESSAGE_MAX_SIZE];
|
|
||||||
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
|
|
||||||
|
|
||||||
public ControlMessageReader() {
|
|
||||||
// invariant: the buffer is always in "get" mode
|
|
||||||
buffer.limit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isFull() {
|
|
||||||
return buffer.remaining() == rawBuffer.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void readFrom(InputStream input) throws IOException {
|
|
||||||
if (isFull()) {
|
|
||||||
throw new IllegalStateException("Buffer full, call next() to consume");
|
|
||||||
}
|
|
||||||
buffer.compact();
|
|
||||||
int head = buffer.position();
|
|
||||||
int r = input.read(rawBuffer, head, rawBuffer.length - head);
|
|
||||||
if (r == -1) {
|
|
||||||
throw new EOFException("Controller socket closed");
|
|
||||||
}
|
|
||||||
buffer.position(head + r);
|
|
||||||
buffer.flip();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ControlMessage next() {
|
|
||||||
if (!buffer.hasRemaining()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int savedPosition = buffer.position();
|
|
||||||
|
|
||||||
int type = buffer.get();
|
|
||||||
ControlMessage msg;
|
|
||||||
switch (type) {
|
|
||||||
case ControlMessage.TYPE_INJECT_KEYCODE:
|
|
||||||
msg = parseInjectKeycode();
|
|
||||||
break;
|
|
||||||
case ControlMessage.TYPE_INJECT_TEXT:
|
|
||||||
msg = parseInjectText();
|
|
||||||
break;
|
|
||||||
case ControlMessage.TYPE_INJECT_TOUCH_EVENT:
|
|
||||||
msg = parseInjectTouchEvent();
|
|
||||||
break;
|
|
||||||
case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
|
|
||||||
msg = parseInjectScrollEvent();
|
|
||||||
break;
|
|
||||||
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
|
|
||||||
msg = parseBackOrScreenOnEvent();
|
|
||||||
break;
|
|
||||||
case ControlMessage.TYPE_GET_CLIPBOARD:
|
|
||||||
msg = parseGetClipboard();
|
|
||||||
break;
|
|
||||||
case ControlMessage.TYPE_SET_CLIPBOARD:
|
|
||||||
msg = parseSetClipboard();
|
|
||||||
break;
|
|
||||||
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
|
|
||||||
msg = parseSetScreenPowerMode();
|
|
||||||
break;
|
|
||||||
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
|
||||||
case ControlMessage.TYPE_EXPAND_SETTINGS_PANEL:
|
|
||||||
case ControlMessage.TYPE_COLLAPSE_PANELS:
|
|
||||||
case ControlMessage.TYPE_ROTATE_DEVICE:
|
|
||||||
case ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
|
||||||
msg = ControlMessage.createEmpty(type);
|
|
||||||
break;
|
|
||||||
case ControlMessage.TYPE_UHID_CREATE:
|
|
||||||
msg = parseUhidCreate();
|
|
||||||
break;
|
|
||||||
case ControlMessage.TYPE_UHID_INPUT:
|
|
||||||
msg = parseUhidInput();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Ln.w("Unknown event type: " + type);
|
|
||||||
msg = null;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg == null) {
|
|
||||||
// failure, reset savedPosition
|
|
||||||
buffer.position(savedPosition);
|
|
||||||
}
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ControlMessage parseInjectKeycode() {
|
|
||||||
if (buffer.remaining() < INJECT_KEYCODE_PAYLOAD_LENGTH) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int action = Binary.toUnsigned(buffer.get());
|
|
||||||
int keycode = buffer.getInt();
|
|
||||||
int repeat = buffer.getInt();
|
|
||||||
int metaState = buffer.getInt();
|
|
||||||
return ControlMessage.createInjectKeycode(action, keycode, repeat, metaState);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int parseBufferLength(int sizeBytes) {
|
|
||||||
assert sizeBytes > 0 && sizeBytes <= 4;
|
|
||||||
if (buffer.remaining() < sizeBytes) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
int value = 0;
|
|
||||||
for (int i = 0; i < sizeBytes; ++i) {
|
|
||||||
value = (value << 8) | (buffer.get() & 0xFF);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String parseString() {
|
|
||||||
int len = parseBufferLength(4);
|
|
||||||
if (len == -1 || buffer.remaining() < len) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int position = buffer.position();
|
|
||||||
// Move the buffer position to consume the text
|
|
||||||
buffer.position(position + len);
|
|
||||||
return new String(rawBuffer, position, len, StandardCharsets.UTF_8);
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] parseByteArray(int sizeBytes) {
|
|
||||||
int len = parseBufferLength(sizeBytes);
|
|
||||||
if (len == -1 || buffer.remaining() < len) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
byte[] data = new byte[len];
|
|
||||||
buffer.get(data);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ControlMessage parseInjectText() {
|
|
||||||
String text = parseString();
|
|
||||||
if (text == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return ControlMessage.createInjectText(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ControlMessage parseInjectTouchEvent() {
|
|
||||||
if (buffer.remaining() < INJECT_TOUCH_EVENT_PAYLOAD_LENGTH) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int action = Binary.toUnsigned(buffer.get());
|
|
||||||
long pointerId = buffer.getLong();
|
|
||||||
Position position = readPosition(buffer);
|
|
||||||
float pressure = Binary.u16FixedPointToFloat(buffer.getShort());
|
|
||||||
int actionButton = buffer.getInt();
|
|
||||||
int buttons = buffer.getInt();
|
|
||||||
return ControlMessage.createInjectTouchEvent(action, pointerId, position, pressure, actionButton, buttons);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ControlMessage parseInjectScrollEvent() {
|
|
||||||
if (buffer.remaining() < INJECT_SCROLL_EVENT_PAYLOAD_LENGTH) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Position position = readPosition(buffer);
|
|
||||||
float hScroll = Binary.i16FixedPointToFloat(buffer.getShort());
|
|
||||||
float vScroll = Binary.i16FixedPointToFloat(buffer.getShort());
|
|
||||||
int buttons = buffer.getInt();
|
|
||||||
return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll, buttons);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ControlMessage parseBackOrScreenOnEvent() {
|
|
||||||
if (buffer.remaining() < BACK_OR_SCREEN_ON_LENGTH) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int action = Binary.toUnsigned(buffer.get());
|
|
||||||
return ControlMessage.createBackOrScreenOn(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ControlMessage parseGetClipboard() {
|
|
||||||
if (buffer.remaining() < GET_CLIPBOARD_LENGTH) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int copyKey = Binary.toUnsigned(buffer.get());
|
|
||||||
return ControlMessage.createGetClipboard(copyKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ControlMessage parseSetClipboard() {
|
|
||||||
if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
long sequence = buffer.getLong();
|
|
||||||
boolean paste = buffer.get() != 0;
|
|
||||||
String text = parseString();
|
|
||||||
if (text == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return ControlMessage.createSetClipboard(sequence, text, paste);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ControlMessage parseSetScreenPowerMode() {
|
|
||||||
if (buffer.remaining() < SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int mode = buffer.get();
|
|
||||||
return ControlMessage.createSetScreenPowerMode(mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ControlMessage parseUhidCreate() {
|
|
||||||
if (buffer.remaining() < UHID_CREATE_FIXED_PAYLOAD_LENGTH) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int id = buffer.getShort();
|
|
||||||
byte[] data = parseByteArray(2);
|
|
||||||
if (data == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return ControlMessage.createUhidCreate(id, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ControlMessage parseUhidInput() {
|
|
||||||
if (buffer.remaining() < UHID_INPUT_FIXED_PAYLOAD_LENGTH) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int id = buffer.getShort();
|
|
||||||
byte[] data = parseByteArray(2);
|
|
||||||
if (data == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return ControlMessage.createUhidInput(id, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Position readPosition(ByteBuffer buffer) {
|
|
||||||
int x = buffer.getInt();
|
|
||||||
int y = buffer.getInt();
|
|
||||||
int screenWidth = Binary.toUnsigned(buffer.getShort());
|
|
||||||
int screenHeight = Binary.toUnsigned(buffer.getShort());
|
|
||||||
return new Position(x, y, screenWidth, screenHeight);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
package com.genymobile.scrcpy;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
public class DeviceMessageWriter {
|
|
||||||
|
|
||||||
private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k
|
|
||||||
public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 5; // type: 1 byte; length: 4 bytes
|
|
||||||
|
|
||||||
private final byte[] rawBuffer = new byte[MESSAGE_MAX_SIZE];
|
|
||||||
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
|
|
||||||
|
|
||||||
public void writeTo(DeviceMessage msg, OutputStream output) throws IOException {
|
|
||||||
buffer.clear();
|
|
||||||
buffer.put((byte) msg.getType());
|
|
||||||
switch (msg.getType()) {
|
|
||||||
case DeviceMessage.TYPE_CLIPBOARD:
|
|
||||||
String text = msg.getText();
|
|
||||||
byte[] raw = text.getBytes(StandardCharsets.UTF_8);
|
|
||||||
int len = StringUtils.getUtf8TruncationIndex(raw, CLIPBOARD_TEXT_MAX_LENGTH);
|
|
||||||
buffer.putInt(len);
|
|
||||||
buffer.put(raw, 0, len);
|
|
||||||
output.write(rawBuffer, 0, buffer.position());
|
|
||||||
break;
|
|
||||||
case DeviceMessage.TYPE_ACK_CLIPBOARD:
|
|
||||||
buffer.putLong(msg.getSequence());
|
|
||||||
output.write(rawBuffer, 0, buffer.position());
|
|
||||||
break;
|
|
||||||
case DeviceMessage.TYPE_UHID_OUTPUT:
|
|
||||||
buffer.putShort((short) msg.getId());
|
|
||||||
byte[] data = msg.getData();
|
|
||||||
buffer.putShort((short) data.length);
|
|
||||||
buffer.put(data);
|
|
||||||
output.write(rawBuffer, 0, buffer.position());
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Ln.w("Unknown device message: " + msg.getType());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,15 @@
|
|||||||
package com.genymobile.scrcpy;
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
import com.genymobile.scrcpy.audio.AudioCodec;
|
||||||
|
import com.genymobile.scrcpy.audio.AudioSource;
|
||||||
|
import com.genymobile.scrcpy.device.Size;
|
||||||
|
import com.genymobile.scrcpy.util.CodecOption;
|
||||||
|
import com.genymobile.scrcpy.util.Ln;
|
||||||
|
import com.genymobile.scrcpy.video.CameraAspectRatio;
|
||||||
|
import com.genymobile.scrcpy.video.CameraFacing;
|
||||||
|
import com.genymobile.scrcpy.video.VideoCodec;
|
||||||
|
import com.genymobile.scrcpy.video.VideoSource;
|
||||||
|
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -16,9 +26,10 @@ public class Options {
|
|||||||
private AudioCodec audioCodec = AudioCodec.OPUS;
|
private AudioCodec audioCodec = AudioCodec.OPUS;
|
||||||
private VideoSource videoSource = VideoSource.DISPLAY;
|
private VideoSource videoSource = VideoSource.DISPLAY;
|
||||||
private AudioSource audioSource = AudioSource.OUTPUT;
|
private AudioSource audioSource = AudioSource.OUTPUT;
|
||||||
|
private boolean audioDup;
|
||||||
private int videoBitRate = 8000000;
|
private int videoBitRate = 8000000;
|
||||||
private int audioBitRate = 128000;
|
private int audioBitRate = 128000;
|
||||||
private int maxFps;
|
private float maxFps;
|
||||||
private int lockVideoOrientation = -1;
|
private int lockVideoOrientation = -1;
|
||||||
private boolean tunnelForward;
|
private boolean tunnelForward;
|
||||||
private Rect crop;
|
private Rect crop;
|
||||||
@ -90,6 +101,10 @@ public class Options {
|
|||||||
return audioSource;
|
return audioSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean getAudioDup() {
|
||||||
|
return audioDup;
|
||||||
|
}
|
||||||
|
|
||||||
public int getVideoBitRate() {
|
public int getVideoBitRate() {
|
||||||
return videoBitRate;
|
return videoBitRate;
|
||||||
}
|
}
|
||||||
@ -98,7 +113,7 @@ public class Options {
|
|||||||
return audioBitRate;
|
return audioBitRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getMaxFps() {
|
public float getMaxFps() {
|
||||||
return maxFps;
|
return maxFps;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,6 +308,9 @@ public class Options {
|
|||||||
}
|
}
|
||||||
options.audioSource = audioSource;
|
options.audioSource = audioSource;
|
||||||
break;
|
break;
|
||||||
|
case "audio_dup":
|
||||||
|
options.audioDup = Boolean.parseBoolean(value);
|
||||||
|
break;
|
||||||
case "max_size":
|
case "max_size":
|
||||||
options.maxSize = Integer.parseInt(value) & ~7; // multiple of 8
|
options.maxSize = Integer.parseInt(value) & ~7; // multiple of 8
|
||||||
break;
|
break;
|
||||||
@ -303,7 +321,7 @@ public class Options {
|
|||||||
options.audioBitRate = Integer.parseInt(value);
|
options.audioBitRate = Integer.parseInt(value);
|
||||||
break;
|
break;
|
||||||
case "max_fps":
|
case "max_fps":
|
||||||
options.maxFps = Integer.parseInt(value);
|
options.maxFps = parseFloat("max_fps", value);
|
||||||
break;
|
break;
|
||||||
case "lock_video_orientation":
|
case "lock_video_orientation":
|
||||||
options.lockVideoOrientation = Integer.parseInt(value);
|
options.lockVideoOrientation = Integer.parseInt(value);
|
||||||
@ -438,8 +456,14 @@ public class Options {
|
|||||||
}
|
}
|
||||||
int width = Integer.parseInt(tokens[0]);
|
int width = Integer.parseInt(tokens[0]);
|
||||||
int height = Integer.parseInt(tokens[1]);
|
int height = Integer.parseInt(tokens[1]);
|
||||||
|
if (width <= 0 || height <= 0) {
|
||||||
|
throw new IllegalArgumentException("Invalid crop size: " + width + "x" + height);
|
||||||
|
}
|
||||||
int x = Integer.parseInt(tokens[2]);
|
int x = Integer.parseInt(tokens[2]);
|
||||||
int y = Integer.parseInt(tokens[3]);
|
int y = Integer.parseInt(tokens[3]);
|
||||||
|
if (x < 0 || y < 0) {
|
||||||
|
throw new IllegalArgumentException("Invalid crop offset: " + x + ":" + y);
|
||||||
|
}
|
||||||
return new Rect(x, y, x + width, y + height);
|
return new Rect(x, y, x + width, y + height);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -469,4 +493,12 @@ public class Options {
|
|||||||
float floatAr = Float.parseFloat(tokens[0]);
|
float floatAr = Float.parseFloat(tokens[0]);
|
||||||
return CameraAspectRatio.fromFloat(floatAr);
|
return CameraAspectRatio.fromFloat(floatAr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static float parseFloat(String key, String value) {
|
||||||
|
try {
|
||||||
|
return Float.parseFloat(value);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new IllegalArgumentException("Invalid float value for " + key + ": \"" + value + "\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,29 @@
|
|||||||
package com.genymobile.scrcpy;
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
import com.genymobile.scrcpy.audio.AudioCapture;
|
||||||
|
import com.genymobile.scrcpy.audio.AudioCodec;
|
||||||
|
import com.genymobile.scrcpy.audio.AudioDirectCapture;
|
||||||
|
import com.genymobile.scrcpy.audio.AudioEncoder;
|
||||||
|
import com.genymobile.scrcpy.audio.AudioPlaybackCapture;
|
||||||
|
import com.genymobile.scrcpy.audio.AudioRawRecorder;
|
||||||
|
import com.genymobile.scrcpy.audio.AudioSource;
|
||||||
|
import com.genymobile.scrcpy.control.ControlChannel;
|
||||||
|
import com.genymobile.scrcpy.control.Controller;
|
||||||
|
import com.genymobile.scrcpy.control.DeviceMessage;
|
||||||
|
import com.genymobile.scrcpy.device.ConfigurationException;
|
||||||
|
import com.genymobile.scrcpy.device.DesktopConnection;
|
||||||
|
import com.genymobile.scrcpy.device.Device;
|
||||||
|
import com.genymobile.scrcpy.device.Streamer;
|
||||||
|
import com.genymobile.scrcpy.util.Ln;
|
||||||
|
import com.genymobile.scrcpy.util.LogUtils;
|
||||||
|
import com.genymobile.scrcpy.util.Settings;
|
||||||
|
import com.genymobile.scrcpy.util.SettingsException;
|
||||||
|
import com.genymobile.scrcpy.video.CameraCapture;
|
||||||
|
import com.genymobile.scrcpy.video.ScreenCapture;
|
||||||
|
import com.genymobile.scrcpy.video.SurfaceCapture;
|
||||||
|
import com.genymobile.scrcpy.video.SurfaceEncoder;
|
||||||
|
import com.genymobile.scrcpy.video.VideoSource;
|
||||||
|
|
||||||
import android.os.BatteryManager;
|
import android.os.BatteryManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
@ -120,7 +144,7 @@ public final class Server {
|
|||||||
|
|
||||||
final Device device = camera ? null : new Device(options);
|
final Device device = camera ? null : new Device(options);
|
||||||
|
|
||||||
Workarounds.apply(audio, camera);
|
Workarounds.apply();
|
||||||
|
|
||||||
List<AsyncProcessor> asyncProcessors = new ArrayList<>();
|
List<AsyncProcessor> asyncProcessors = new ArrayList<>();
|
||||||
|
|
||||||
@ -142,7 +166,14 @@ public final class Server {
|
|||||||
|
|
||||||
if (audio) {
|
if (audio) {
|
||||||
AudioCodec audioCodec = options.getAudioCodec();
|
AudioCodec audioCodec = options.getAudioCodec();
|
||||||
AudioCapture audioCapture = new AudioCapture(options.getAudioSource());
|
AudioSource audioSource = options.getAudioSource();
|
||||||
|
AudioCapture audioCapture;
|
||||||
|
if (audioSource.isDirect()) {
|
||||||
|
audioCapture = new AudioDirectCapture(audioSource);
|
||||||
|
} else {
|
||||||
|
audioCapture = new AudioPlaybackCapture(options.getAudioDup());
|
||||||
|
}
|
||||||
|
|
||||||
Streamer audioStreamer = new Streamer(connection.getAudioFd(), audioCodec, options.getSendCodecMeta(), options.getSendFrameMeta());
|
Streamer audioStreamer = new Streamer(connection.getAudioFd(), audioCodec, options.getSendCodecMeta(), options.getSendFrameMeta());
|
||||||
AsyncProcessor audioRecorder;
|
AsyncProcessor audioRecorder;
|
||||||
if (audioCodec == AudioCodec.RAW) {
|
if (audioCodec == AudioCodec.RAW) {
|
||||||
@ -248,7 +279,7 @@ public final class Server {
|
|||||||
Ln.i(LogUtils.buildDisplayListMessage());
|
Ln.i(LogUtils.buildDisplayListMessage());
|
||||||
}
|
}
|
||||||
if (options.getListCameras() || options.getListCameraSizes()) {
|
if (options.getListCameras() || options.getListCameraSizes()) {
|
||||||
Workarounds.apply(false, true);
|
Workarounds.apply();
|
||||||
Ln.i(LogUtils.buildCameraListMessage(options.getListCameraSizes()));
|
Ln.i(LogUtils.buildCameraListMessage(options.getListCameraSizes()));
|
||||||
}
|
}
|
||||||
// Just print the requested data, do not mirror
|
// Just print the requested data, do not mirror
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
package com.genymobile.scrcpy;
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
import com.genymobile.scrcpy.audio.AudioCaptureException;
|
||||||
|
import com.genymobile.scrcpy.util.Ln;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
@ -48,62 +51,25 @@ public final class Workarounds {
|
|||||||
// not instantiable
|
// not instantiable
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void apply(boolean audio, boolean camera) {
|
public static void apply() {
|
||||||
boolean mustFillConfigurationController = false;
|
|
||||||
boolean mustFillAppInfo = false;
|
|
||||||
boolean mustFillAppContext = false;
|
|
||||||
|
|
||||||
if (Build.BRAND.equalsIgnoreCase("meizu")) {
|
|
||||||
// Workarounds must be applied for Meizu phones:
|
|
||||||
// - <https://github.com/Genymobile/scrcpy/issues/240>
|
|
||||||
// - <https://github.com/Genymobile/scrcpy/issues/365>
|
|
||||||
// - <https://github.com/Genymobile/scrcpy/issues/2656>
|
|
||||||
//
|
|
||||||
// But only apply when strictly necessary, since workarounds can cause other issues:
|
|
||||||
// - <https://github.com/Genymobile/scrcpy/issues/940>
|
|
||||||
// - <https://github.com/Genymobile/scrcpy/issues/994>
|
|
||||||
mustFillAppInfo = true;
|
|
||||||
} else if (Build.BRAND.equalsIgnoreCase("honor")) {
|
|
||||||
// More workarounds must be applied for Honor devices:
|
|
||||||
// - <https://github.com/Genymobile/scrcpy/issues/4015>
|
|
||||||
//
|
|
||||||
// The system context must not be set for all devices, because it would cause other problems:
|
|
||||||
// - <https://github.com/Genymobile/scrcpy/issues/4015#issuecomment-1595382142>
|
|
||||||
// - <https://github.com/Genymobile/scrcpy/issues/3805#issuecomment-1596148031>
|
|
||||||
mustFillAppInfo = true;
|
|
||||||
mustFillAppContext = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (audio && Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
|
|
||||||
// Before Android 11, audio is not supported.
|
|
||||||
// Since Android 12, we can properly set a context on the AudioRecord.
|
|
||||||
// Only on Android 11 we must fill the application context for the AudioRecord to work.
|
|
||||||
mustFillAppContext = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (camera) {
|
|
||||||
mustFillAppInfo = true;
|
|
||||||
mustFillAppContext = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
// On some Samsung devices, DisplayManagerGlobal.getDisplayInfoLocked() calls ActivityThread.currentActivityThread().getConfiguration(),
|
// On some Samsung devices, DisplayManagerGlobal.getDisplayInfoLocked() calls ActivityThread.currentActivityThread().getConfiguration(),
|
||||||
// which requires a non-null ConfigurationController.
|
// which requires a non-null ConfigurationController.
|
||||||
// ConfigurationController was introduced in Android 12, so do not attempt to set it on lower versions.
|
// ConfigurationController was introduced in Android 12, so do not attempt to set it on lower versions.
|
||||||
// <https://github.com/Genymobile/scrcpy/issues/4467>
|
// <https://github.com/Genymobile/scrcpy/issues/4467>
|
||||||
mustFillConfigurationController = true;
|
// Must be called before fillAppContext() because it is necessary to get a valid system context.
|
||||||
}
|
|
||||||
|
|
||||||
if (mustFillConfigurationController) {
|
|
||||||
// Must be call before fillAppContext() because it is necessary to get a valid system context
|
|
||||||
fillConfigurationController();
|
fillConfigurationController();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// On ONYX devices, fillAppInfo() breaks video mirroring:
|
||||||
|
// <https://github.com/Genymobile/scrcpy/issues/5182>
|
||||||
|
boolean mustFillAppInfo = !Build.BRAND.equalsIgnoreCase("ONYX");
|
||||||
|
|
||||||
if (mustFillAppInfo) {
|
if (mustFillAppInfo) {
|
||||||
fillAppInfo();
|
fillAppInfo();
|
||||||
}
|
}
|
||||||
if (mustFillAppContext) {
|
|
||||||
fillAppContext();
|
fillAppContext();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
@ -191,7 +157,8 @@ public final class Workarounds {
|
|||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.R)
|
@TargetApi(Build.VERSION_CODES.R)
|
||||||
@SuppressLint("WrongConstant,MissingPermission")
|
@SuppressLint("WrongConstant,MissingPermission")
|
||||||
public static AudioRecord createAudioRecord(int source, int sampleRate, int channelConfig, int channels, int channelMask, int encoding) {
|
public static AudioRecord createAudioRecord(int source, int sampleRate, int channelConfig, int channels, int channelMask, int encoding) throws
|
||||||
|
AudioCaptureException {
|
||||||
// Vivo (and maybe some other third-party ROMs) modified `AudioRecord`'s constructor, requiring `Context`s from real App environment.
|
// Vivo (and maybe some other third-party ROMs) modified `AudioRecord`'s constructor, requiring `Context`s from real App environment.
|
||||||
//
|
//
|
||||||
// This method invokes the `AudioRecord(long nativeRecordInJavaObj)` constructor to create an empty `AudioRecord` instance, then uses
|
// This method invokes the `AudioRecord(long nativeRecordInJavaObj)` constructor to create an empty `AudioRecord` instance, then uses
|
||||||
@ -332,8 +299,8 @@ public final class Workarounds {
|
|||||||
|
|
||||||
return audioRecord;
|
return audioRecord;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Ln.e("Failed to invoke AudioRecord.<init>.", e);
|
Ln.e("Cannot create AudioRecord", e);
|
||||||
throw new RuntimeException("Cannot create AudioRecord");
|
throw new AudioCaptureException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
package com.genymobile.scrcpy.audio;
|
||||||
|
|
||||||
|
import android.media.MediaCodec;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
public interface AudioCapture {
|
||||||
|
void checkCompatibility() throws AudioCaptureException;
|
||||||
|
void start() throws AudioCaptureException;
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a chunk of {@link AudioConfig#MAX_READ_SIZE} samples.
|
||||||
|
*
|
||||||
|
* @param outDirectBuffer The target buffer
|
||||||
|
* @param outBufferInfo The info to provide to MediaCodec
|
||||||
|
* @return the number of bytes actually read.
|
||||||
|
*/
|
||||||
|
int read(ByteBuffer outDirectBuffer, MediaCodec.BufferInfo outBufferInfo);
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user