Compare commits

..

1 Commits

Author SHA1 Message Date
77ebafd96c Retrieve icon decoder directly
The call to av_find_best_stream() gives the decoder directly, there is
no need to retrieve it afterwards in a separate step.
2024-06-11 08:58:19 +02:00
159 changed files with 1552 additions and 4047 deletions

3
.github/FUNDING.yml vendored
View File

@ -1,3 +0,0 @@
github: [rom1v]
liberapay: rom1v
custom: ["https://paypal.me/rom2v"]

View File

@ -7,25 +7,17 @@ assignees: ''
---
_Please read the [prerequisites] to run scrcpy._
- [ ] I have read the [FAQ](https://github.com/Genymobile/scrcpy/blob/master/FAQ.md).
- [ ] I have searched in existing [issues](https://github.com/Genymobile/scrcpy/issues).
[prerequisites]: https://github.com/Genymobile/scrcpy#prerequisites
_Also read the [FAQ] and check if your [issue][issues] already exists._
[FAQ]: https://github.com/Genymobile/scrcpy/blob/master/FAQ.md
[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
**Environment**
- OS: [e.g. Debian, Windows, macOS...]
- scrcpy version: [e.g. 1.12.1]
- installation method: [e.g. manual build, apt, snap, brew, Windows release...]
- device model:
- Android version: [e.g. 10]
**Describe the bug**
A clear and concise description of what the bug is.
On errors, please provide the output of the console (and `adb logcat` if relevant).

View File

@ -1,8 +0,0 @@
---
name: Question
about: Ask a question about scrcpy
title: ''
labels: ''
assignees: ''
---

View File

@ -2,7 +2,7 @@
source for the project. Do not download releases from random websites, even if
their name contains `scrcpy`.**
# scrcpy (v2.6.1)
# scrcpy (v2.4)
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
@ -53,16 +53,10 @@ Make sure you [enabled USB debugging][enable-adb] on your device(s).
[enable-adb]: https://developer.android.com/studio/debug/dev-options#enable
On some devices (especially Xiaomi), you might get the following error:
```
java.lang.SecurityException: Injecting input events requires the caller (or the source of the instrumentation, if any) to have the INJECT_EVENTS permission.
```
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.
On some devices, you also 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
@ -154,14 +148,11 @@ documented in the following pages:
## Contact
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.
If you encounter a bug, please read the [FAQ](FAQ.md) first, then open an [issue].
[issue]: https://github.com/Genymobile/scrcpy/issues
You can also use:
For general questions or discussions, you can also use:
- Reddit: [`r/scrcpy`](https://www.reddit.com/r/scrcpy)
- Twitter: [`@scrcpy_app`](https://twitter.com/scrcpy_app)

View File

@ -6,7 +6,6 @@ _scrcpy() {
--audio-buffer=
--audio-codec=
--audio-codec-options=
--audio-dup
--audio-encoder=
--audio-source=
--audio-output-buffer=
@ -26,6 +25,7 @@ _scrcpy() {
-e --select-tcpip
-f --fullscreen
--force-adb-forward
--forward-all-clicks
-h --help
-K
--keyboard=
@ -41,7 +41,6 @@ _scrcpy() {
-M
--max-fps=
--mouse=
--mouse-bind=
-n --no-control
-N --no-playback
--no-audio
@ -51,7 +50,6 @@ _scrcpy() {
--no-downsize-on-error
--no-key-repeat
--no-mipmaps
--no-mouse-hover
--no-power-on
--no-video
--no-video-playback
@ -112,7 +110,7 @@ _scrcpy() {
return
;;
--audio-source)
COMPREPLY=($(compgen -W 'output mic playback' -- "$cur"))
COMPREPLY=($(compgen -W 'output mic' -- "$cur"))
return
;;
--camera-facing)

View File

@ -13,9 +13,8 @@ arguments=(
'--audio-buffer=[Configure the audio buffering delay (in milliseconds)]'
'--audio-codec=[Select the audio codec]:codec:(opus aac flac raw)'
'--audio-codec-options=[Set a list of comma-separated key\:type=value options for the device audio encoder]'
'--audio-dup=[Duplicate audio]'
'--audio-encoder=[Use a specific MediaCodec audio encoder]'
'--audio-source=[Select the audio source]:source:(output mic playback)'
'--audio-source=[Select the audio source]:source:(output mic)'
'--audio-output-buffer=[Configure the size of the SDL audio output buffer (in milliseconds)]'
{-b,--video-bit-rate=}'[Encode the video at the given bit-rate]'
'--camera-ar=[Select the camera size by its aspect ratio]'
@ -33,9 +32,10 @@ arguments=(
{-e,--select-tcpip}'[Use TCP/IP device]'
{-f,--fullscreen}'[Start in fullscreen]'
'--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]'
'--forward-all-clicks[Forward clicks to device]'
{-h,--help}'[Print the help]'
'-K[Use UHID keyboard (same as --keyboard=uhid)]'
'--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]'
'--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]'
'--list-camera-sizes[List the valid camera capture sizes]'
@ -46,8 +46,7 @@ arguments=(
{-m,--max-size=}'[Limit both the width and height of the video to value]'
'-M[Use UHID mouse (same as --mouse=uhid)]'
'--max-fps=[Limit the frame rate of screen capture]'
'--mouse=[Set the mouse input mode]:mode:(disabled sdk uhid aoa)'
'--mouse-bind=[Configure bindings of secondary clicks]'
'--mouse[Set the mouse input mode]:mode:(disabled sdk uhid aoa)'
{-n,--no-control}'[Disable device control \(mirror the device in read only\)]'
{-N,--no-playback}'[Disable video and audio playback]'
'--no-audio[Disable audio forwarding]'
@ -57,7 +56,6 @@ arguments=(
'--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-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-video[Disable video forwarding]'
'--no-video-playback[Disable video playback]'

View File

@ -4,10 +4,10 @@ DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
VERSION=35.0.0
VERSION=34.0.5
FILENAME=platform-tools_r$VERSION-windows.zip
PROJECT_DIR=platform-tools-$VERSION
SHA256SUM=7ab78a8f8b305ae4d0de647d99c43599744de61a0838d3a47bda0cdffefee87e
SHA256SUM=3f8320152704377de150418a3c4c9d07d16d80a6c0d0d8f7289c22c499e33571
cd "$SOURCES_DIR"

View File

@ -4,10 +4,10 @@ DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
VERSION=7.0.1
VERSION=6.1.1
FILENAME=ffmpeg-$VERSION.tar.xz
PROJECT_DIR=ffmpeg-$VERSION
SHA256SUM=bce9eeb0f17ef8982390b1f37711a61b4290dc8c2a0c1a37b5857e85bfb0e4ff
SHA256SUM=8684f4b00f94b85461884c3719382f1261f0d9eb3d59640a1f4ac0873616f968
cd "$SOURCES_DIR"
@ -17,6 +17,7 @@ then
else
get_file "https://ffmpeg.org/releases/$FILENAME" "$FILENAME" "$SHA256SUM"
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
patch -d "$PROJECT_DIR" -p1 < "$PATCHES_DIR"/ffmpeg-6.1-fix-build.patch
fi
mkdir -p "$BUILD_DIR/$PROJECT_DIR"

View File

@ -0,0 +1,27 @@
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

View File

@ -4,10 +4,10 @@ DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
VERSION=2.30.5
VERSION=2.28.5
FILENAME=SDL-$VERSION.tar.gz
PROJECT_DIR=SDL-release-$VERSION
SHA256SUM=be3ca88f8c362704627a0bc5406edb2cd6cc6ba463596d81ebb7c2f18763d3bf
SHA256SUM=9f0556e4a24ef5b267010038ad9e9948b62f236d5bcc4b22179f95ef62d84023
cd "$SOURCES_DIR"

View File

@ -15,7 +15,6 @@ src = [
'src/demuxer.c',
'src/device_msg.c',
'src/display.c',
'src/events.c',
'src/icon.c',
'src/file_pusher.c',
'src/fps_counter.c',
@ -32,12 +31,10 @@ src = [
'src/screen.c',
'src/server.c',
'src/version.c',
'src/hid/hid_gamepad.c',
'src/hid/hid_keyboard.c',
'src/hid/hid_mouse.c',
'src/trait/frame_source.c',
'src/trait/packet_source.c',
'src/uhid/gamepad_uhid.c',
'src/uhid/keyboard_uhid.c',
'src/uhid/mouse_uhid.c',
'src/uhid/uhid_output.c',
@ -96,7 +93,6 @@ usb_support = get_option('usb')
if usb_support
src += [
'src/usb/aoa_hid.c',
'src/usb/gamepad_aoa.c',
'src/usb/keyboard_aoa.c',
'src/usb/mouse_aoa.c',
'src/usb/scrcpy_otg.c',

View File

@ -13,7 +13,7 @@ BEGIN
VALUE "LegalCopyright", "Romain Vimont, Genymobile"
VALUE "OriginalFilename", "scrcpy.exe"
VALUE "ProductName", "scrcpy"
VALUE "ProductVersion", "2.6.1"
VALUE "ProductVersion", "2.4"
END
END
BLOCK "VarFileInfo"

View File

@ -29,7 +29,7 @@ Default is 128K (128000).
.BI "\-\-audio\-buffer " ms
Configure the audio buffering delay (in milliseconds).
Lower values decrease the latency, but increase the likelihood of buffer underrun (causing audio glitches).
Lower values decrease the latency, but increase the likelyhood of buffer underrun (causing audio glitches).
Default is 50.
@ -49,12 +49,6 @@ The list of possible codec options is available in the Android documentation:
<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
.BI "\-\-audio\-encoder " name
Use a specific MediaCodec audio encoder (depending on the codec provided by \fB\-\-audio\-codec\fR).
@ -63,13 +57,7 @@ The available encoders can be listed by \fB\-\-list\-encoders\fR.
.TP
.BI "\-\-audio\-source " source
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.
Select the audio source (output or mic).
Default is output.
@ -176,27 +164,16 @@ Start in fullscreen.
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.
.B \-\-forward\-all\-clicks
By default, right-click triggers BACK (or POWER on) and middle-click triggers HOME. This option disables these shortcuts and forward the clicks to the device instead.
.TP
.BI "\-\-gamepad " mode
Select how to send gamepad inputs to the device.
Possible values are "disabled", "uhid" and "aoa":
- "disabled" does not send keyboard inputs to the device.
- "uhid" simulates a physical HID gamepad using the Linux HID kernel module on the device.
- "aoa" simulates a physical HID gamepad using the AOAv2 protocol. It may only work over USB.
Also see \fB\-\-keyboard\f and R\fB\-\-mouse\fR.
.TP
.B \-h, \-\-help
Print this help.
.TP
.B \-K
Same as \fB\-\-keyboard=uhid\fR, or \fB\-\-keyboard=aoa\fR if \fB\-\-otg\fR is set.
Same as \fB\-\-keyboard=uhid\fR.
.TP
.BI "\-\-keyboard " mode
@ -215,7 +192,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).
Also see \fB\-\-mouse\fR and \fB\-\-gamepad\fR.
Also see \fB\-\-mouse\fR.
.TP
.B \-\-kill\-adb\-on\-close
@ -261,7 +238,7 @@ Default is 0 (unlimited).
.TP
.B \-M
Same as \fB\-\-mouse=uhid\fR, or \fB\-\-mouse=aoa\fR if \fB\-\-otg\fR is set.
Same as \fB\-\-mouse=uhid\fR.
.TP
.BI "\-\-max\-fps " value
@ -282,28 +259,7 @@ 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.
Also see \fB\-\-keyboard\fR and \fB\-\-gamepad\fR.
.TP
.BI "\-\-mouse\-bind " xxxx[:xxxx]
Configure bindings of secondary clicks.
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:
- '+': forward the click to the device
- '-': ignore the click
- 'b': trigger shortcut BACK (or turn screen on if off)
- 'h': trigger shortcut HOME
- 's': trigger shortcut APP_SWITCH
- 'n': trigger shortcut "expand notification panel"
Default is 'bhsn:++++' for SDK mouse, and '++++:bhsn' for AOA and UHID.
Also see \fB\-\-keyboard\fR.
.TP
@ -348,10 +304,6 @@ Do not forward repeated key events when a key is held down.
.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.
.TP
.B \-\-no\-mouse\-hover
Do not forward mouse hover (mouse motion without any clicks) events.
.TP
.B \-\-no\-power\-on
Do not power on the device on start.
@ -384,7 +336,7 @@ If any of \fB\-\-hid\-keyboard\fR or \fB\-\-hid\-mouse\fR is set, only enable ke
It may only work over USB.
See \fB\-\-keyboard\fR, \fB\-\-mouse\fR and \fB\-\-gamepad\fR.
See \fB\-\-hid\-keyboard\fR and \fB\-\-hid\-mouse\fR.
.TP
.BI "\-p, \-\-port " port\fR[:\fIport\fR]
@ -394,7 +346,7 @@ Default is 27183:27199.
.TP
\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 occurred).
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).
This is useful to prevent the terminal window from automatically closing, so that error messages can be read.
@ -472,9 +424,9 @@ Turn the device screen off immediately.
.BI "\-\-shortcut\-mod " key\fR[+...]][,...]
Specify the modifiers to use for scrcpy shortcuts. Possible keys are "lctrl", "rctrl", "lalt", "ralt", "lsuper" and "rsuper".
Several shortcut modifiers can be specified, separated by ','.
A shortcut can consist in several keys, separated by '+'. Several shortcuts can be specified, separated by ','.
For example, to use either LCtrl or LSuper for scrcpy shortcuts, pass "lctrl,lsuper".
For example, to use either LCtrl+LAlt or LSuper for scrcpy shortcuts, pass "lctrl+lalt,lsuper".
Default is "lalt,lsuper" (left-Alt or left-Super).

View File

@ -633,7 +633,7 @@ enum android_keycode {
* Toggles between BS and CS digital satellite services. */
AKEYCODE_TV_SATELLITE_SERVICE = 240,
/** Toggle Network key.
* Toggles selecting broadcast services. */
* Toggles selecting broacast services. */
AKEYCODE_TV_NETWORK = 241,
/** Antenna/Cable key.
* Toggles broadcast input source between antenna and cable. */

View File

@ -98,10 +98,6 @@ enum {
OPT_HID_KEYBOARD_DEPRECATED,
OPT_HID_MOUSE_DEPRECATED,
OPT_NO_WINDOW,
OPT_MOUSE_BIND,
OPT_NO_MOUSE_HOVER,
OPT_AUDIO_DUP,
OPT_GAMEPAD,
};
struct sc_option {
@ -157,7 +153,7 @@ static const struct sc_option options[] = {
.argdesc = "ms",
.text = "Configure the audio buffering delay (in milliseconds).\n"
"Lower values decrease the latency, but increase the "
"likelihood of buffer underrun (causing audio glitches).\n"
"likelyhood of buffer underrun (causing audio glitches).\n"
"Default is 50.",
},
{
@ -179,13 +175,6 @@ static const struct sc_option options[] = {
"Android documentation: "
"<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 = "audio-encoder",
@ -198,13 +187,7 @@ static const struct sc_option options[] = {
.longopt_id = OPT_AUDIO_SOURCE,
.longopt = "audio-source",
.argdesc = "source",
.text = "Select the audio source (output, mic or playback).\n"
"The \"output\" source forwards the whole audio output, and "
"disables playback on the device.\n"
"The \"playback\" source captures the audio playback (Android "
"apps can opt-out, so the whole output is not necessarily "
"captured).\n"
"The \"mic\" source captures the microphone.\n"
.text = "Select the audio source (output or mic).\n"
"Default is output.",
},
{
@ -369,26 +352,11 @@ static const struct sc_option options[] = {
"device.",
},
{
// deprecated
.longopt_id = OPT_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 a physical HID gamepad using the Linux "
"UHID kernel module on the device.\n"
"\"aoa\" simulates a physical gamepad using the AOAv2 "
"protocol. It may only work over USB.\n"
"Also see --keyboard and --mouse.",
.text = "By default, right-click triggers BACK (or POWER on) and "
"middle-click triggers HOME. This option disables these "
"shortcuts and forwards the clicks to the device instead.",
},
{
.shortopt = 'h',
@ -397,7 +365,7 @@ static const struct sc_option options[] = {
},
{
.shortopt = 'K',
.text = "Same as --keyboard=uhid, or --keyboard=aoa if --otg is set.",
.text = "Same as --keyboard=uhid.",
},
{
.longopt_id = OPT_KEYBOARD,
@ -421,7 +389,7 @@ static const struct sc_option options[] = {
"start -a android.settings.HARD_KEYBOARD_SETTINGS`.\n"
"This option is only available when a HID keyboard is enabled "
"(or a physical keyboard is connected).\n"
"Also see --mouse and --gamepad.",
"Also see --mouse.",
},
{
.longopt_id = OPT_KILL_ADB_ON_CLOSE,
@ -493,7 +461,7 @@ static const struct sc_option options[] = {
},
{
.shortopt = 'M',
.text = "Same as --mouse=uhid, or --mouse=aoa if --otg is set.",
.text = "Same as --mouse=uhid.",
},
{
.longopt_id = OPT_MAX_FPS,
@ -520,31 +488,7 @@ static const struct sc_option options[] = {
"to control the device directly (relative mouse mode).\n"
"LAlt, LSuper or RSuper toggle the capture mode, to give "
"control of the mouse back to the computer.\n"
"Also see --keyboard and --gamepad.",
},
{
.longopt_id = OPT_MOUSE_BIND,
.longopt = "mouse-bind",
.argdesc = "xxxx[:xxxx]",
.text = "Configure bindings of secondary clicks.\n"
"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).\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"
" '+': forward the click to the device\n"
" '-': ignore the click\n"
" 'b': trigger shortcut BACK (or turn screen on if off)\n"
" 'h': trigger shortcut HOME\n"
" 's': trigger shortcut APP_SWITCH\n"
" 'n': trigger shortcut \"expand notification panel\"\n"
"Default is 'bhsn:++++' for SDK mouse, and '++++:bhsn' for AOA "
"and UHID.",
"Also see --keyboard.",
},
{
.shortopt = 'n',
@ -608,12 +552,6 @@ static const struct sc_option options[] = {
"mipmaps are automatically generated to improve downscaling "
"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 = "no-power-on",
@ -655,7 +593,7 @@ static const struct sc_option options[] = {
"Keyboard and mouse may be disabled separately using"
"--keyboard=disabled and --mouse=disabled.\n"
"It may only work over USB.\n"
"See --keyboard, --mouse and --gamepad.",
"See --keyboard and --mouse.",
},
{
.shortopt = 'p',
@ -672,7 +610,7 @@ static const struct sc_option options[] = {
.optional_arg = true,
.text = "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).\n"
"\"if-error\" (pause only if an error occured).\n"
"This is useful to prevent the terminal window from "
"automatically closing, so that error messages can be read.\n"
"Default is \"false\".\n"
@ -778,10 +716,10 @@ static const struct sc_option options[] = {
.text = "Specify the modifiers to use for scrcpy shortcuts.\n"
"Possible keys are \"lctrl\", \"rctrl\", \"lalt\", \"ralt\", "
"\"lsuper\" and \"rsuper\".\n"
"Several shortcut modifiers can be specified, separated by "
"','.\n"
"For example, to use either LCtrl or LSuper for scrcpy "
"shortcuts, pass \"lctrl,lsuper\".\n"
"A shortcut can consist in several keys, separated by '+'. "
"Several shortcuts can be specified, separated by ','.\n"
"For example, to use either LCtrl+LAlt or LSuper for scrcpy "
"shortcuts, pass \"lctrl+lalt,lsuper\".\n"
"Default is \"lalt,lsuper\" (left-Alt or left-Super).",
},
{
@ -1367,7 +1305,7 @@ print_exit_status(const struct sc_exit_status *status, unsigned cols) {
return;
}
assert(strlen(text) >= 9); // Contains at least the initial indentation
assert(strlen(text) >= 9); // Contains at least the initial identation
// text + 9 to remove the initial indentation
printf(" %3d %s\n", status->value, text + 9);
@ -1749,62 +1687,82 @@ parse_log_level(const char *s, enum sc_log_level *log_level) {
return false;
}
static enum sc_shortcut_mod
// item is a list of mod keys separated by '+' (e.g. "lctrl+lalt")
// returns a bitwise-or of SC_SHORTCUT_MOD_* constants (or 0 on error)
static unsigned
parse_shortcut_mods_item(const char *item, size_t len) {
unsigned mod = 0;
for (;;) {
char *plus = strchr(item, '+');
// strchr() does not consider the "len" parameter, to it could find an
// occurrence too far in the string (there is no strnchr())
bool has_plus = plus && plus < item + len;
assert(!has_plus || plus > item);
size_t key_len = has_plus ? (size_t) (plus - item) : len;
#define STREQ(literal, s, len) \
((sizeof(literal)-1 == len) && !memcmp(literal, s, len))
if (STREQ("lctrl", item, len)) {
return SC_SHORTCUT_MOD_LCTRL;
}
if (STREQ("rctrl", item, len)) {
return SC_SHORTCUT_MOD_RCTRL;
}
if (STREQ("lalt", item, len)) {
return SC_SHORTCUT_MOD_LALT;
}
if (STREQ("ralt", item, len)) {
return SC_SHORTCUT_MOD_RALT;
}
if (STREQ("lsuper", item, len)) {
return SC_SHORTCUT_MOD_LSUPER;
}
if (STREQ("rsuper", item, len)) {
return SC_SHORTCUT_MOD_RSUPER;
}
if (STREQ("lctrl", item, key_len)) {
mod |= SC_SHORTCUT_MOD_LCTRL;
} else if (STREQ("rctrl", item, key_len)) {
mod |= SC_SHORTCUT_MOD_RCTRL;
} else if (STREQ("lalt", item, key_len)) {
mod |= SC_SHORTCUT_MOD_LALT;
} else if (STREQ("ralt", item, key_len)) {
mod |= SC_SHORTCUT_MOD_RALT;
} else if (STREQ("lsuper", item, key_len)) {
mod |= SC_SHORTCUT_MOD_LSUPER;
} else if (STREQ("rsuper", item, key_len)) {
mod |= SC_SHORTCUT_MOD_RSUPER;
} else {
LOGE("Unknown modifier key: %.*s "
"(must be one of: lctrl, rctrl, lalt, ralt, lsuper, rsuper)",
(int) key_len, item);
return 0;
}
#undef STREQ
bool has_plus = strchr(item, '+');
if (has_plus) {
LOGE("Shortcut mod combination with '+' is not supported anymore: "
"'%.*s' (see #4741)", (int) len, item);
return 0;
if (!has_plus) {
break;
}
item = plus + 1;
assert(len >= key_len + 1);
len -= key_len + 1;
}
LOGE("Unknown modifier key: %.*s "
"(must be one of: lctrl, rctrl, lalt, ralt, lsuper, rsuper)",
(int) len, item);
return 0;
return mod;
}
static bool
parse_shortcut_mods(const char *s, uint8_t *shortcut_mods) {
uint8_t mods = 0;
parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods) {
unsigned count = 0;
unsigned current = 0;
// A list of shortcut modifiers, for example "lctrl,rctrl,rsuper"
// LCtrl+LAlt or RCtrl or LCtrl+RSuper: "lctrl+lalt,rctrl,lctrl+rsuper"
for (;;) {
char *comma = strchr(s, ',');
assert(!comma || comma > s);
size_t limit = comma ? (size_t) (comma - s) : strlen(s);
enum sc_shortcut_mod mod = parse_shortcut_mods_item(s, limit);
if (!mod) {
if (comma && count == SC_MAX_SHORTCUT_MODS - 1) {
assert(count < SC_MAX_SHORTCUT_MODS);
LOGW("Too many shortcut modifiers alternatives");
return false;
}
mods |= mod;
assert(!comma || comma > s);
size_t limit = comma ? (size_t) (comma - s) : strlen(s);
unsigned mod = parse_shortcut_mods_item(s, limit);
if (!mod) {
LOGE("Invalid modifier keys: %.*s", (int) limit, s);
return false;
}
mods->data[current++] = mod;
++count;
if (!comma) {
break;
@ -1813,7 +1771,7 @@ parse_shortcut_mods(const char *s, uint8_t *shortcut_mods) {
s = comma + 1;
}
*shortcut_mods = mods;
mods->count = count;
return true;
}
@ -1821,7 +1779,7 @@ parse_shortcut_mods(const char *s, uint8_t *shortcut_mods) {
#ifdef SC_TEST
// expose the function to unit-tests
bool
sc_parse_shortcut_mods(const char *s, uint8_t *mods) {
sc_parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods) {
return parse_shortcut_mods(s, mods);
}
#endif
@ -1963,13 +1921,7 @@ parse_audio_source(const char *optarg, enum sc_audio_source *source) {
return true;
}
if (!strcmp(optarg, "playback")) {
*source = SC_AUDIO_SOURCE_PLAYBACK;
return true;
}
LOGE("Unsupported audio source: %s (expected output, mic or playback)",
optarg);
LOGE("Unsupported audio source: %s (expected output or mic)", optarg);
return false;
}
@ -2076,32 +2028,6 @@ parse_mouse(const char *optarg, enum sc_mouse_input_mode *mode) {
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
parse_time_limit(const char *s, sc_tick *tick) {
long value;
@ -2132,85 +2058,11 @@ parse_pause_on_exit(const char *s, enum sc_pause_on_exit *pause_on_exit) {
}
LOGE("Unsupported pause on exit mode: %s "
"(expected true, false or if-error)", s);
"(expected true, false or if-error)", optarg);
return false;
}
static bool
parse_mouse_binding(char c, enum sc_mouse_binding *b) {
switch (c) {
case '+':
*b = SC_MOUSE_BINDING_CLICK;
return true;
case '-':
*b = SC_MOUSE_BINDING_DISABLED;
return true;
case 'b':
*b = SC_MOUSE_BINDING_BACK;
return true;
case 'h':
*b = SC_MOUSE_BINDING_HOME;
return true;
case 's':
*b = SC_MOUSE_BINDING_APP_SWITCH;
return true;
case 'n':
*b = SC_MOUSE_BINDING_EXPAND_NOTIFICATION_PANEL;
return true;
default:
LOGE("Invalid mouse binding: '%c' "
"(expected '+', '-', 'b', 'h', 's' or 'n')", c);
return false;
}
}
static bool
parse_mouse_binding_set(const char *s, struct sc_mouse_binding_set *mbs) {
assert(strlen(s) >= 4);
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 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;
}
if (!parse_mouse_binding_set(s, &mb->pri)) {
return false;
}
if (len == 9) {
if (!parse_mouse_binding_set(s + 5, &mb->sec)) {
return false;
}
} else {
// use the same bindings for Shift+click
mb->sec = mb->pri;
}
return true;
}
static bool
parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
const char *optstring, const struct option *longopts) {
@ -2264,7 +2116,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
args->help = true;
break;
case 'K':
opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_UHID_OR_AOA;
opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_UHID;
break;
case OPT_KEYBOARD:
if (!parse_keyboard(optarg, &opts->keyboard_input_mode)) {
@ -2286,21 +2138,13 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
}
break;
case 'M':
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_UHID_OR_AOA;
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_UHID;
break;
case OPT_MOUSE:
if (!parse_mouse(optarg, &opts->mouse_input_mode)) {
return false;
}
break;
case OPT_MOUSE_BIND:
if (!parse_mouse_bindings(optarg, &opts->mouse_bindings)) {
return false;
}
break;
case OPT_NO_MOUSE_HOVER:
opts->mouse_hover = false;
break;
case OPT_HID_MOUSE_DEPRECATED:
LOGE("--hid-mouse has been removed, use --mouse=aoa or "
"--mouse=uhid instead.");
@ -2498,22 +2342,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
}
break;
case OPT_FORWARD_ALL_CLICKS:
LOGW("--forward-all-clicks is deprecated, "
"use --mouse-bind=++++ instead.");
opts->mouse_bindings = (struct sc_mouse_bindings) {
.pri = {
.right_click = SC_MOUSE_BINDING_CLICK,
.middle_click = 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,
},
};
opts->forward_all_clicks = true;
break;
case OPT_LEGACY_PASTE:
opts->legacy_paste = true;
@ -2667,17 +2496,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
case OPT_NO_WINDOW:
opts->window = false;
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:
// getopt prints the error message on stderr
return false;
@ -2795,12 +2613,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
if (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AUTO) {
opts->keyboard_input_mode = otg ? SC_KEYBOARD_INPUT_MODE_AOA
: SC_KEYBOARD_INPUT_MODE_SDK;
} 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 (otg) {
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_AOA;
@ -2810,59 +2623,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
} else {
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_SDK;
} else if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK
&& !opts->video_playback) {
LOGE("SDK mouse mode requires video playback. Try --mouse=uhid.");
return false;
}
if (opts->gamepad_input_mode == SC_GAMEPAD_INPUT_MODE_AUTO) {
// UHID does not work on all devices (with old Android
// versions), so it cannot be enabled by default
opts->gamepad_input_mode = otg ? SC_GAMEPAD_INPUT_MODE_AOA
: SC_GAMEPAD_INPUT_MODE_DISABLED;
} else 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 explicitly set, configure default bindings
if (opts->mouse_bindings.pri.right_click == SC_MOUSE_BINDING_AUTO) {
assert(opts->mouse_bindings.pri.middle_click == SC_MOUSE_BINDING_AUTO);
assert(opts->mouse_bindings.pri.click4 == 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
if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK) {
opts->mouse_bindings.pri = default_shortcuts;
opts->mouse_bindings.sec = forward;
} else {
opts->mouse_bindings.pri = forward;
opts->mouse_bindings.sec = default_shortcuts;
}
}
if (otg) {
@ -2909,12 +2674,6 @@ 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) {
LOGI("Tunnel host/port is set, "
"--force-adb-forward automatically enabled.");
@ -2966,31 +2725,13 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
if (opts->audio && opts->audio_source == SC_AUDIO_SOURCE_AUTO) {
// Select the audio source according to the video source
if (opts->video_source == SC_VIDEO_SOURCE_DISPLAY) {
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;
}
opts->audio_source = SC_AUDIO_SOURCE_OUTPUT;
} else {
opts->audio_source = SC_AUDIO_SOURCE_MIC;
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) {
LOGE("Record format specified without recording");
return false;
@ -3172,7 +2913,7 @@ sc_get_pause_on_exit(int argc, char *argv[]) {
if (!strcmp(value, "if-error")) {
return SC_PAUSE_ON_EXIT_IF_ERROR;
}
// Set to false, including when the value is invalid
// Set to false, inclusing when the value is invalid
return SC_PAUSE_ON_EXIT_FALSE;
}
}

View File

@ -28,7 +28,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]);
#ifdef SC_TEST
bool
sc_parse_shortcut_mods(const char *s, uint8_t *shortcut_mods);
sc_parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods);
#endif
#endif

View File

@ -64,11 +64,13 @@ static const char *const copy_key_labels[] = {
static inline const char *
get_well_known_pointer_id_name(uint64_t pointer_id) {
switch (pointer_id) {
case SC_POINTER_ID_MOUSE:
case POINTER_ID_MOUSE:
return "mouse";
case SC_POINTER_ID_GENERIC_FINGER:
case POINTER_ID_GENERIC_FINGER:
return "finger";
case SC_POINTER_ID_VIRTUAL_FINGER:
case POINTER_ID_VIRTUAL_MOUSE:
return "vmouse";
case POINTER_ID_VIRTUAL_FINGER:
return "vfinger";
default:
return NULL;
@ -155,9 +157,6 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
sc_write16be(&buf[3], msg->uhid_input.size);
memcpy(&buf[5], msg->uhid_input.data, 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_SETTINGS_PANEL:
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
@ -272,9 +271,6 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
}
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:
LOG_CMSG("open hard keyboard settings");
break;
@ -284,16 +280,6 @@ 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
sc_control_msg_destroy(struct sc_control_msg *msg) {
switch (msg->type) {

View File

@ -18,11 +18,12 @@
// 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_POINTER_ID_MOUSE UINT64_C(-1)
#define SC_POINTER_ID_GENERIC_FINGER UINT64_C(-2)
#define POINTER_ID_MOUSE UINT64_C(-1)
#define POINTER_ID_GENERIC_FINGER UINT64_C(-2)
// Used for injecting an additional virtual pointer for pinch-to-zoom
#define SC_POINTER_ID_VIRTUAL_FINGER UINT64_C(-3)
#define POINTER_ID_VIRTUAL_MOUSE UINT64_C(-3)
#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-4)
enum sc_control_msg_type {
SC_CONTROL_MSG_TYPE_INJECT_KEYCODE,
@ -39,7 +40,6 @@ enum sc_control_msg_type {
SC_CONTROL_MSG_TYPE_ROTATE_DEVICE,
SC_CONTROL_MSG_TYPE_UHID_CREATE,
SC_CONTROL_MSG_TYPE_UHID_INPUT,
SC_CONTROL_MSG_TYPE_UHID_DESTROY,
SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS,
};
@ -106,9 +106,6 @@ struct sc_control_msg {
uint16_t size;
uint8_t data[SC_HID_MAX_SIZE];
} uhid_input;
struct {
uint16_t id;
} uhid_destroy;
};
};
@ -120,11 +117,6 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf);
void
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
sc_control_msg_destroy(struct sc_control_msg *msg);

View File

@ -4,17 +4,15 @@
#include "util/log.h"
// Drop droppable events above this limit
#define SC_CONTROL_MSG_QUEUE_LIMIT 60
#define SC_CONTROL_MSG_QUEUE_MAX 64
static void
sc_controller_receiver_on_ended(struct sc_receiver *receiver, bool error,
void *userdata) {
sc_controller_receiver_on_error(struct sc_receiver *receiver, void *userdata) {
(void) receiver;
struct sc_controller *controller = userdata;
// Forward the event to the controller listener
controller->cbs->on_ended(controller, error, controller->cbs_userdata);
controller->cbs->on_error(controller, controller->cbs_userdata);
}
bool
@ -23,15 +21,13 @@ sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
void *cbs_userdata) {
sc_vecdeque_init(&controller->queue);
// Add 4 to support 4 non-droppable events without re-allocation
bool ok = sc_vecdeque_reserve(&controller->queue,
SC_CONTROL_MSG_QUEUE_LIMIT + 4);
bool ok = sc_vecdeque_reserve(&controller->queue, SC_CONTROL_MSG_QUEUE_MAX);
if (!ok) {
return false;
}
static const struct sc_receiver_callbacks receiver_cbs = {
.on_ended = sc_controller_receiver_on_ended,
.on_error = sc_controller_receiver_on_error,
};
ok = sc_receiver_init(&controller->receiver, control_socket, &receiver_cbs,
@ -59,7 +55,7 @@ sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
controller->control_socket = control_socket;
controller->stopped = false;
assert(cbs && cbs->on_ended);
assert(cbs && cbs->on_error);
controller->cbs = cbs;
controller->cbs_userdata = cbs_userdata;
@ -96,59 +92,39 @@ sc_controller_push_msg(struct sc_controller *controller,
sc_control_msg_log(msg);
}
bool pushed = false;
sc_mutex_lock(&controller->mutex);
size_t size = sc_vecdeque_size(&controller->queue);
if (size < SC_CONTROL_MSG_QUEUE_LIMIT) {
bool full = sc_vecdeque_is_full(&controller->queue);
if (!full) {
bool was_empty = sc_vecdeque_is_empty(&controller->queue);
sc_vecdeque_push_noresize(&controller->queue, *msg);
pushed = true;
if (was_empty) {
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
sc_mutex_unlock(&controller->mutex);
return pushed;
return !full;
}
static bool
process_msg(struct sc_controller *controller,
const struct sc_control_msg *msg, bool *eos) {
const struct sc_control_msg *msg) {
static uint8_t serialized_msg[SC_CONTROL_MSG_MAX_SIZE];
size_t length = sc_control_msg_serialize(msg, serialized_msg);
if (!length) {
*eos = false;
return false;
}
ssize_t w =
net_send_all(controller->control_socket, serialized_msg, length);
if ((size_t) w != length) {
*eos = true;
return false;
}
return true;
return (size_t) w == length;
}
static int
run_controller(void *data) {
struct sc_controller *controller = data;
bool error = false;
for (;;) {
sc_mutex_lock(&controller->mutex);
while (!controller->stopped
@ -158,7 +134,6 @@ run_controller(void *data) {
if (controller->stopped) {
// stop immediately, do not process further msgs
sc_mutex_unlock(&controller->mutex);
LOGD("Controller stopped");
break;
}
@ -166,21 +141,20 @@ run_controller(void *data) {
struct sc_control_msg msg = sc_vecdeque_pop(&controller->queue);
sc_mutex_unlock(&controller->mutex);
bool eos;
bool ok = process_msg(controller, &msg, &eos);
bool ok = process_msg(controller, &msg);
sc_control_msg_destroy(&msg);
if (!ok) {
if (eos) {
LOGD("Controller stopped (socket closed)");
} // else error already logged
error = !eos;
break;
LOGD("Could not write msg to socket");
goto error;
}
}
controller->cbs->on_ended(controller, error, controller->cbs_userdata);
return 0;
error:
controller->cbs->on_error(controller, controller->cbs_userdata);
return 1; // ignored
}
bool

View File

@ -28,8 +28,7 @@ struct sc_controller {
};
struct sc_controller_callbacks {
void (*on_ended)(struct sc_controller *controller, bool error,
void *userdata);
void (*on_error)(struct sc_controller *controller, void *userdata);
};
bool

View File

@ -278,6 +278,7 @@ run_demuxer(void *data) {
finally_close_sinks:
sc_packet_source_sinks_close(&demuxer->packet_source);
finally_free_context:
// This also calls avcodec_close() internally
avcodec_free_context(&codec_ctx);
end:
demuxer->cbs->on_ended(demuxer, status, demuxer->cbs_userdata);

View File

@ -43,10 +43,6 @@ sc_display_init(struct sc_display *display, SDL_Window *window,
display->mipmaps = false;
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
display->gl_context = NULL;
#endif
// starts with "opengl"
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
if (use_opengl) {

View File

@ -1,66 +0,0 @@
#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("Coud not post 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);
}

View File

@ -1,38 +1,10 @@
#ifndef SC_EVENTS_H
#define SC_EVENTS_H
#include "common.h"
#include <stdbool.h>
#include <stdint.h>
#include <SDL_events.h>
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
#define SC_EVENT_NEW_FRAME SDL_USEREVENT
#define SC_EVENT_DEVICE_DISCONNECTED (SDL_USEREVENT + 1)
#define SC_EVENT_SERVER_CONNECTION_FAILED (SDL_USEREVENT + 2)
#define SC_EVENT_SERVER_CONNECTED (SDL_USEREVENT + 3)
#define SC_EVENT_USB_DEVICE_DISCONNECTED (SDL_USEREVENT + 4)
#define SC_EVENT_DEMUXER_ERROR (SDL_USEREVENT + 5)
#define SC_EVENT_RECORDER_ERROR (SDL_USEREVENT + 6)
#define SC_EVENT_SCREEN_INIT_SIZE (SDL_USEREVENT + 7)
#define SC_EVENT_TIME_LIMIT_REACHED (SDL_USEREVENT + 8)
#define SC_EVENT_CONTROLLER_ERROR (SDL_USEREVENT + 9)

View File

@ -5,22 +5,11 @@
#include <stdint.h>
#define SC_HID_MAX_SIZE 15
#define SC_HID_MAX_SIZE 8
struct sc_hid_input {
uint16_t hid_id;
struct sc_hid_event {
uint8_t data[SC_HID_MAX_SIZE];
uint8_t size;
};
struct sc_hid_open {
uint16_t hid_id;
const uint8_t *report_desc; // pointer to static memory
size_t report_desc_size;
};
struct sc_hid_close {
uint16_t hid_id;
};
#endif

View File

@ -1,452 +0,0 @@
#include "hid_gamepad.h"
#include <assert.h>
#include <inttypes.h>
#include "util/binary.h"
#include "util/log.h"
// 2 bytes for left stick (X, Y)
// 2 bytes for right stick (Z, Rz)
// 2 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) are stored
// locally in the MSB, 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 events.
*
* 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, 0x0F,
// 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 event 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 state (0-65535)
* byte 1: |. . . . . . . .|
* +---------------+
* byte 2: |. . . . . . . .|
* | | left stick y state (0-65535)
* byte 3: |. . . . . . . .|
* +---------------+
* byte 4: |. . . . . . . .|
* | | right stick x state (0-65535)
* byte 5: |. . . . . . . .|
* +---------------+
* byte 6: |. . . . . . . .|
* | | right stick y state (0-65535)
* byte 7: |. . . . . . . .|
* +---------------+
* byte 8: |. . . . . . . .|
* | | L2 trigger state (0-65535)
* byte 9: |. . . . . . . .|
* +---------------+
* byte 10: |. . . . . . . .|
* | | R2 trigger state (0-65535)
* byte 11: |. . . . . . . .|
* +---------------+
*
* ,--------------- 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);
uint16_t hid_id = sc_hid_gamepad_slot_get_id(slot_idx);
hid_open->hid_id = hid_id;
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;
}

View File

@ -1,53 +0,0 @@
#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 2
#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

View File

@ -21,7 +21,7 @@
// keyboard support, though OS could support more keys via modifying the report
// desc. 6 should be enough for scrcpy.
#define SC_HID_KEYBOARD_MAX_KEYS 6
#define SC_HID_KEYBOARD_INPUT_SIZE \
#define SC_HID_KEYBOARD_EVENT_SIZE \
(SC_HID_KEYBOARD_INDEX_KEYS + SC_HID_KEYBOARD_MAX_KEYS)
#define SC_HID_RESERVED 0x00
@ -31,16 +31,13 @@
* For HID, only report descriptor is needed.
*
* The specification is available here:
* <https://www.usb.org/document-library/device-class-definition-hid-111>
* <https://www.usb.org/sites/default/files/hid1_11.pdf>
*
* In particular, read:
* - §6.2.2 Report Descriptor
* - 6.2.2 Report Descriptor
* - Appendix B.1 Protocol 1 (Keyboard)
* - 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:
* Modifier Reserved Key Key Key Key Key Key
*
@ -50,7 +47,7 @@
*
* (change vid:pid' to your device's vendor ID and product ID).
*/
static const uint8_t SC_HID_KEYBOARD_REPORT_DESC[] = {
const uint8_t SC_HID_KEYBOARD_REPORT_DESC[] = {
// Usage Page (Generic Desktop)
0x05, 0x01,
// Usage (Keyboard)
@ -63,7 +60,7 @@ static const uint8_t SC_HID_KEYBOARD_REPORT_DESC[] = {
0x05, 0x07,
// Usage Minimum (224)
0x19, 0xE0,
// Usage Maximum (231)
// Usage Maximum (231)
0x29, 0xE7,
// Logical Minimum (0)
0x15, 0x00,
@ -124,6 +121,9 @@ static const uint8_t SC_HID_KEYBOARD_REPORT_DESC[] = {
0xC0
};
const size_t SC_HID_KEYBOARD_REPORT_DESC_LEN =
sizeof(SC_HID_KEYBOARD_REPORT_DESC);
/**
* A keyboard HID event is 8 bytes long:
*
@ -199,11 +199,10 @@ static const uint8_t SC_HID_KEYBOARD_REPORT_DESC[] = {
*/
static void
sc_hid_keyboard_input_init(struct sc_hid_input *hid_input) {
hid_input->hid_id = SC_HID_ID_KEYBOARD;
hid_input->size = SC_HID_KEYBOARD_INPUT_SIZE;
sc_hid_keyboard_event_init(struct sc_hid_event *hid_event) {
hid_event->size = SC_HID_KEYBOARD_EVENT_SIZE;
uint8_t *data = hid_input->data;
uint8_t *data = hid_event->data;
data[SC_HID_KEYBOARD_INDEX_MODS] = SC_HID_MOD_NONE;
data[1] = SC_HID_RESERVED;
@ -251,9 +250,9 @@ scancode_is_modifier(enum sc_scancode scancode) {
}
bool
sc_hid_keyboard_generate_input_from_key(struct sc_hid_keyboard *hid,
struct sc_hid_input *hid_input,
const struct sc_key_event *event) {
sc_hid_keyboard_event_from_key(struct sc_hid_keyboard *hid,
struct sc_hid_event *hid_event,
const struct sc_key_event *event) {
enum sc_scancode scancode = event->scancode;
assert(scancode >= 0);
@ -265,7 +264,7 @@ sc_hid_keyboard_generate_input_from_key(struct sc_hid_keyboard *hid,
return false;
}
sc_hid_keyboard_input_init(hid_input);
sc_hid_keyboard_event_init(hid_event);
uint16_t mods = sc_hid_mod_from_sdl_keymod(event->mods_state);
@ -276,9 +275,9 @@ sc_hid_keyboard_generate_input_from_key(struct sc_hid_keyboard *hid,
hid->keys[scancode] ? "true" : "false");
}
hid_input->data[SC_HID_KEYBOARD_INDEX_MODS] = mods;
hid_event->data[SC_HID_KEYBOARD_INDEX_MODS] = mods;
uint8_t *keys_data = &hid_input->data[SC_HID_KEYBOARD_INDEX_KEYS];
uint8_t *keys_data = &hid_event->data[SC_HID_KEYBOARD_INDEX_KEYS];
// Re-calculate pressed keys every time
int keys_pressed_count = 0;
for (int i = 0; i < SC_HID_KEYBOARD_KEYS; ++i) {
@ -309,8 +308,8 @@ end:
}
bool
sc_hid_keyboard_generate_input_from_mods(struct sc_hid_input *hid_input,
uint16_t mods_state) {
sc_hid_keyboard_event_from_mods(struct sc_hid_event *event,
uint16_t mods_state) {
bool capslock = mods_state & SC_MOD_CAPS;
bool numlock = mods_state & SC_MOD_NUM;
if (!capslock && !numlock) {
@ -318,27 +317,17 @@ sc_hid_keyboard_generate_input_from_mods(struct sc_hid_input *hid_input,
return false;
}
sc_hid_keyboard_input_init(hid_input);
sc_hid_keyboard_event_init(event);
unsigned i = 0;
if (capslock) {
hid_input->data[SC_HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_CAPSLOCK;
event->data[SC_HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_CAPSLOCK;
++i;
}
if (numlock) {
hid_input->data[SC_HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_NUMLOCK;
event->data[SC_HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_NUMLOCK;
++i;
}
return true;
}
void sc_hid_keyboard_generate_open(struct sc_hid_open *hid_open) {
hid_open->hid_id = SC_HID_ID_KEYBOARD;
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;
}

View File

@ -14,7 +14,8 @@
// 0x65 is Application, typically AT-101 Keyboard ends here.
#define SC_HID_KEYBOARD_KEYS 0x66
#define SC_HID_ID_KEYBOARD 1
extern const uint8_t SC_HID_KEYBOARD_REPORT_DESC[];
extern const size_t SC_HID_KEYBOARD_REPORT_DESC_LEN;
/**
* HID keyboard events are sequence-based, every time keyboard state changes
@ -35,19 +36,13 @@ struct sc_hid_keyboard {
void
sc_hid_keyboard_init(struct sc_hid_keyboard *hid);
void
sc_hid_keyboard_generate_open(struct sc_hid_open *hid_open);
void
sc_hid_keyboard_generate_close(struct sc_hid_close *hid_close);
bool
sc_hid_keyboard_event_from_key(struct sc_hid_keyboard *hid,
struct sc_hid_event *hid_event,
const struct sc_key_event *event);
bool
sc_hid_keyboard_generate_input_from_key(struct sc_hid_keyboard *hid,
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);
sc_hid_keyboard_event_from_mods(struct sc_hid_event *event,
uint16_t mods_state);
#endif

View File

@ -2,17 +2,17 @@
// 1 byte for buttons + padding, 1 byte for X position, 1 byte for Y position,
// 1 byte for wheel motion
#define SC_HID_MOUSE_INPUT_SIZE 4
#define HID_MOUSE_EVENT_SIZE 4
/**
* Mouse descriptor from the specification:
* <https://www.usb.org/document-library/device-class-definition-hid-111>
* <https://www.usb.org/sites/default/files/hid1_11.pdf>
*
* Appendix E (p71): §E.10 Report Descriptor (Mouse)
*
* The usage tags (like Wheel) are listed in "HID Usage Tables":
* <https://www.usb.org/document-library/hid-usage-tables-15>
* §4 Generic Desktop Page (0x01) (p32)
* <https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf>
* §4 Generic Desktop Page (0x01) (p26)
*/
const uint8_t SC_HID_MOUSE_REPORT_DESC[] = {
// Usage Page (Generic Desktop)
@ -34,7 +34,7 @@ const uint8_t SC_HID_MOUSE_REPORT_DESC[] = {
// Usage Minimum (1)
0x19, 0x01,
// Usage Maximum (5)
// Usage Maximum (5)
0x29, 0x05,
// Logical Minimum (0)
0x15, 0x00,
@ -62,9 +62,9 @@ const uint8_t SC_HID_MOUSE_REPORT_DESC[] = {
0x09, 0x31,
// Usage (Wheel)
0x09, 0x38,
// Logical Minimum (-127)
// Local Minimum (-127)
0x15, 0x81,
// Logical Maximum (127)
// Local Maximum (127)
0x25, 0x7F,
// Report Size (8)
0x75, 0x08,
@ -80,6 +80,9 @@ const uint8_t SC_HID_MOUSE_REPORT_DESC[] = {
0xC0,
};
const size_t SC_HID_MOUSE_REPORT_DESC_LEN =
sizeof(SC_HID_MOUSE_REPORT_DESC);
/**
* A mouse HID event is 4 bytes long:
*
@ -122,10 +125,10 @@ const uint8_t SC_HID_MOUSE_REPORT_DESC[] = {
*/
static void
sc_hid_mouse_input_init(struct sc_hid_input *hid_input) {
hid_input->hid_id = SC_HID_ID_MOUSE;
hid_input->size = SC_HID_MOUSE_INPUT_SIZE;
// Leave ->data uninitialized, it will be fully initialized by callers
sc_hid_mouse_event_init(struct sc_hid_event *hid_event) {
hid_event->size = HID_MOUSE_EVENT_SIZE;
// Leave hid_event->data uninitialized, it will be fully initialized by
// callers
}
static uint8_t
@ -150,11 +153,11 @@ sc_hid_buttons_from_buttons_state(uint8_t buttons_state) {
}
void
sc_hid_mouse_generate_input_from_motion(struct sc_hid_input *hid_input,
const struct sc_mouse_motion_event *event) {
sc_hid_mouse_input_init(hid_input);
sc_hid_mouse_event_from_motion(struct sc_hid_event *hid_event,
const struct sc_mouse_motion_event *event) {
sc_hid_mouse_event_init(hid_event);
uint8_t *data = hid_input->data;
uint8_t *data = hid_event->data;
data[0] = sc_hid_buttons_from_buttons_state(event->buttons_state);
data[1] = CLAMP(event->xrel, -127, 127);
data[2] = CLAMP(event->yrel, -127, 127);
@ -162,11 +165,11 @@ sc_hid_mouse_generate_input_from_motion(struct sc_hid_input *hid_input,
}
void
sc_hid_mouse_generate_input_from_click(struct sc_hid_input *hid_input,
const struct sc_mouse_click_event *event) {
sc_hid_mouse_input_init(hid_input);
sc_hid_mouse_event_from_click(struct sc_hid_event *hid_event,
const struct sc_mouse_click_event *event) {
sc_hid_mouse_event_init(hid_event);
uint8_t *data = hid_input->data;
uint8_t *data = hid_event->data;
data[0] = sc_hid_buttons_from_buttons_state(event->buttons_state);
data[1] = 0; // no x motion
data[2] = 0; // no y motion
@ -174,11 +177,11 @@ sc_hid_mouse_generate_input_from_click(struct sc_hid_input *hid_input,
}
void
sc_hid_mouse_generate_input_from_scroll(struct sc_hid_input *hid_input,
const struct sc_mouse_scroll_event *event) {
sc_hid_mouse_input_init(hid_input);
sc_hid_mouse_event_from_scroll(struct sc_hid_event *hid_event,
const struct sc_mouse_scroll_event *event) {
sc_hid_mouse_event_init(hid_event);
uint8_t *data = hid_input->data;
uint8_t *data = hid_event->data;
data[0] = 0; // buttons state irrelevant (and unknown)
data[1] = 0; // no x motion
data[2] = 0; // no y motion
@ -187,13 +190,3 @@ sc_hid_mouse_generate_input_from_scroll(struct sc_hid_input *hid_input,
data[3] = CLAMP(event->vscroll, -127, 127);
// 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->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;
}

View File

@ -1,6 +1,8 @@
#ifndef SC_HID_MOUSE_H
#define SC_HID_MOUSE_H
#endif
#include "common.h"
#include <stdbool.h>
@ -8,24 +10,17 @@
#include "hid/hid_event.h"
#include "input_events.h"
#define SC_HID_ID_MOUSE 2
extern const uint8_t SC_HID_MOUSE_REPORT_DESC[];
extern const size_t SC_HID_MOUSE_REPORT_DESC_LEN;
void
sc_hid_mouse_generate_open(struct sc_hid_open *hid_open);
sc_hid_mouse_event_from_motion(struct sc_hid_event *hid_event,
const struct sc_mouse_motion_event *event);
void
sc_hid_mouse_generate_close(struct sc_hid_close *hid_close);
sc_hid_mouse_event_from_click(struct sc_hid_event *hid_event,
const struct sc_mouse_click_event *event);
void
sc_hid_mouse_generate_input_from_motion(struct sc_hid_input *hid_input,
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
sc_hid_mouse_event_from_scroll(struct sc_hid_event *hid_event,
const struct sc_mouse_scroll_event *event);

View File

@ -78,16 +78,7 @@ decode_image(const char *path) {
goto close_input;
}
// In ffmpeg/doc/APIchanges:
// 2021-04-27 - 46dac8cf3d - lavf 59.0.100 - avformat.h
// av_find_best_stream now uses a const AVCodec ** parameter
// for the returned decoder.
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(59, 0, 100)
const AVCodec *codec;
#else
AVCodec *codec;
#endif
int stream =
av_find_best_stream(ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0);
@ -117,21 +108,21 @@ decode_image(const char *path) {
AVFrame *frame = av_frame_alloc();
if (!frame) {
LOG_OOM();
goto free_codec_ctx;
goto close_codec;
}
AVPacket *packet = av_packet_alloc();
if (!packet) {
LOG_OOM();
av_frame_free(&frame);
goto free_codec_ctx;
goto close_codec;
}
if (av_read_frame(ctx, packet) < 0) {
LOGE("Could not read frame");
av_packet_free(&packet);
av_frame_free(&frame);
goto free_codec_ctx;
goto close_codec;
}
int ret;
@ -139,20 +130,22 @@ decode_image(const char *path) {
LOGE("Could not send icon packet: %d", ret);
av_packet_free(&packet);
av_frame_free(&frame);
goto free_codec_ctx;
goto close_codec;
}
if ((ret = avcodec_receive_frame(codec_ctx, frame)) != 0) {
LOGE("Could not receive icon frame: %d", ret);
av_packet_free(&packet);
av_frame_free(&frame);
goto free_codec_ctx;
goto close_codec;
}
av_packet_free(&packet);
result = frame;
close_codec:
avcodec_close(codec_ctx);
free_codec_ctx:
avcodec_free_context(&codec_ctx);
close_input:

View File

@ -9,7 +9,6 @@
#include <SDL2/SDL_events.h>
#include "coords.h"
#include "options.h"
/* The representation of input events in scrcpy is very close to the SDL API,
* for simplicity.
@ -323,38 +322,6 @@ enum sc_mouse_button {
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),
"SDL_Keymod must be convertible to sc_mod");
@ -412,33 +379,6 @@ struct sc_touch_event {
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
sc_mods_state_from_sdl(uint16_t mods_state) {
return mods_state;
@ -496,50 +436,19 @@ sc_mouse_button_from_sdl(uint8_t button) {
}
static inline uint8_t
sc_mouse_buttons_state_from_sdl(uint32_t buttons_state) {
sc_mouse_buttons_state_from_sdl(uint32_t buttons_state,
bool forward_all_clicks) {
assert(buttons_state < 0x100); // fits in uint8_t
// SC_MOUSE_BUTTON_* constants are initialized from SDL_BUTTON(index)
return buttons_state;
}
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;
uint8_t mask = SC_MOUSE_BUTTON_LEFT;
if (forward_all_clicks) {
mask |= SC_MOUSE_BUTTON_RIGHT
| SC_MOUSE_BUTTON_MIDDLE
| SC_MOUSE_BUTTON_X1
| SC_MOUSE_BUTTON_X2;
}
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) {
// SDL_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;
return buttons_state & mask;
}
#endif

View File

@ -10,7 +10,7 @@
#define SC_SDL_SHORTCUT_MODS_MASK (KMOD_CTRL | KMOD_ALT | KMOD_GUI)
static inline uint16_t
to_sdl_mod(uint8_t shortcut_mod) {
to_sdl_mod(unsigned shortcut_mod) {
uint16_t sdl_mod = 0;
if (shortcut_mod & SC_SHORTCUT_MOD_LCTRL) {
sdl_mod |= KMOD_LCTRL;
@ -38,49 +38,50 @@ is_shortcut_mod(struct sc_input_manager *im, uint16_t sdl_mod) {
// keep only the relevant modifier keys
sdl_mod &= SC_SDL_SHORTCUT_MODS_MASK;
// at least one shortcut mod pressed?
return sdl_mod & im->sdl_shortcut_mods;
}
assert(im->sdl_shortcut_mods.count);
assert(im->sdl_shortcut_mods.count < SC_MAX_SHORTCUT_MODS);
for (unsigned i = 0; i < im->sdl_shortcut_mods.count; ++i) {
if (im->sdl_shortcut_mods.data[i] == sdl_mod) {
return true;
}
}
static bool
is_shortcut_key(struct sc_input_manager *im, SDL_Keycode keycode) {
return (im->sdl_shortcut_mods & KMOD_LCTRL && keycode == SDLK_LCTRL)
|| (im->sdl_shortcut_mods & KMOD_RCTRL && keycode == SDLK_RCTRL)
|| (im->sdl_shortcut_mods & KMOD_LALT && keycode == SDLK_LALT)
|| (im->sdl_shortcut_mods & KMOD_RALT && keycode == SDLK_RALT)
|| (im->sdl_shortcut_mods & KMOD_LGUI && keycode == SDLK_LGUI)
|| (im->sdl_shortcut_mods & KMOD_RGUI && keycode == SDLK_RGUI);
return false;
}
void
sc_input_manager_init(struct sc_input_manager *im,
const struct sc_input_manager_params *params) {
// A key/mouse processor may not be present if there is no controller
assert((!params->kp && !params->mp && !params->gp) || params->controller);
assert((!params->kp && !params->mp) || params->controller);
// A processor must have ops initialized
assert(!params->kp || params->kp->ops);
assert(!params->mp || params->mp->ops);
assert(!params->gp || params->gp->ops);
im->controller = params->controller;
im->fp = params->fp;
im->screen = params->screen;
im->kp = params->kp;
im->mp = params->mp;
im->gp = params->gp;
im->mouse_bindings = params->mouse_bindings;
im->forward_all_clicks = params->forward_all_clicks;
im->legacy_paste = params->legacy_paste;
im->clipboard_autosync = params->clipboard_autosync;
im->sdl_shortcut_mods = to_sdl_mod(params->shortcut_mods);
const struct sc_shortcut_mods *shortcut_mods = params->shortcut_mods;
assert(shortcut_mods->count);
assert(shortcut_mods->count < SC_MAX_SHORTCUT_MODS);
for (unsigned i = 0; i < shortcut_mods->count; ++i) {
uint16_t sdl_mod = to_sdl_mod(shortcut_mods->data[i]);
assert(sdl_mod);
im->sdl_shortcut_mods.data[i] = sdl_mod;
}
im->sdl_shortcut_mods.count = shortcut_mods->count;
im->vfinger_down = false;
im->vfinger_invert_x = false;
im->vfinger_invert_y = false;
im->mouse_buttons_state = 0;
im->last_keycode = SDLK_UNKNOWN;
im->last_mod = 0;
im->key_repeat = 0;
@ -369,7 +370,9 @@ simulate_virtual_finger(struct sc_input_manager *im,
msg.inject_touch_event.action = action;
msg.inject_touch_event.position.screen_size = im->screen->frame_size;
msg.inject_touch_event.position.point = point;
msg.inject_touch_event.pointer_id = SC_POINTER_ID_VIRTUAL_FINGER;
msg.inject_touch_event.pointer_id =
im->forward_all_clicks ? POINTER_ID_VIRTUAL_MOUSE
: POINTER_ID_VIRTUAL_FINGER;
msg.inject_touch_event.pressure = up ? 0.0f : 1.0f;
msg.inject_touch_event.action_button = 0;
msg.inject_touch_event.buttons = 0;
@ -409,12 +412,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
bool shift = event->keysym.mod & KMOD_SHIFT;
bool repeat = event->repeat;
// Either the modifier includes a shortcut modifier, or the key
// press/release is a modifier key.
// The second condition is necessary to ignore the release of the modifier
// key (because in this case mod is 0).
bool is_shortcut = is_shortcut_mod(im, mod)
|| is_shortcut_key(im, keycode);
bool smod = is_shortcut_mod(im, mod);
if (down && !repeat) {
if (keycode == im->last_keycode && mod == im->last_mod) {
@ -426,7 +424,8 @@ sc_input_manager_process_key(struct sc_input_manager *im,
}
}
if (is_shortcut) {
// The shortcut modifier is pressed
if (smod) {
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
switch (keycode) {
case SDLK_h:
@ -654,11 +653,13 @@ sc_input_manager_process_mouse_motion(struct sc_input_manager *im,
struct sc_mouse_motion_event evt = {
.position = sc_input_manager_get_position(im, event->x, event->y),
.pointer_id = im->vfinger_down ? SC_POINTER_ID_GENERIC_FINGER
: SC_POINTER_ID_MOUSE,
.pointer_id = im->forward_all_clicks ? POINTER_ID_MOUSE
: POINTER_ID_GENERIC_FINGER,
.xrel = event->xrel,
.yrel = event->yrel,
.buttons_state = im->mouse_buttons_state,
.buttons_state =
sc_mouse_buttons_state_from_sdl(event->state,
im->forward_all_clicks),
};
assert(im->mp->ops->process_mouse_motion);
@ -709,25 +710,6 @@ sc_input_manager_process_touch(struct sc_input_manager *im,
im->mp->ops->process_touch(im->mp, &evt);
}
static enum sc_mouse_binding
sc_input_manager_get_binding(const struct sc_mouse_binding_set *bindings,
uint8_t sdl_button) {
switch (sdl_button) {
case SDL_BUTTON_LEFT:
return SC_MOUSE_BINDING_CLICK;
case SDL_BUTTON_RIGHT:
return bindings->right_click;
case SDL_BUTTON_MIDDLE:
return bindings->middle_click;
case SDL_BUTTON_X1:
return bindings->click4;
case SDL_BUTTON_X2:
return bindings->click5;
default:
return SC_MOUSE_BINDING_DISABLED;
}
}
static void
sc_input_manager_process_mouse_button(struct sc_input_manager *im,
const SDL_MouseButtonEvent *event) {
@ -739,100 +721,66 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
bool control = im->controller;
bool paused = im->screen->paused;
bool down = event->type == SDL_MOUSEBUTTONDOWN;
if (!im->forward_all_clicks) {
if (control && !paused) {
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
enum sc_mouse_button button = sc_mouse_button_from_sdl(event->button);
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) {
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 =
sc_input_manager_get_binding(bindings, event->button);
assert(binding != SC_MOUSE_BINDING_AUTO);
switch (binding) {
case SC_MOUSE_BINDING_DISABLED:
// ignore click
if (im->kp && event->button == SDL_BUTTON_X1) {
action_app_switch(im, action);
return;
}
if (event->button == SDL_BUTTON_X2 && down) {
if (event->clicks < 2) {
expand_notification_panel(im);
} else {
expand_settings_panel(im);
}
return;
}
if (im->kp && event->button == SDL_BUTTON_RIGHT) {
press_back_or_turn_screen_on(im, action);
return;
}
if (im->kp && event->button == SDL_BUTTON_MIDDLE) {
action_home(im, action);
return;
case SC_MOUSE_BINDING_BACK:
if (im->kp) {
press_back_or_turn_screen_on(im, action);
}
return;
case SC_MOUSE_BINDING_HOME:
if (im->kp) {
action_home(im, action);
}
return;
case SC_MOUSE_BINDING_APP_SWITCH:
if (im->kp) {
action_app_switch(im, action);
}
return;
case SC_MOUSE_BINDING_EXPAND_NOTIFICATION_PANEL:
if (down) {
if (event->clicks < 2) {
expand_notification_panel(im);
} else {
expand_settings_panel(im);
}
}
return;
default:
assert(binding == SC_MOUSE_BINDING_CLICK);
break;
}
}
// double-click on black borders resizes to fit the device screen
bool video = im->screen->video;
bool mouse_relative_mode = im->mp && im->mp->relative_mode;
if (video && !mouse_relative_mode && event->button == SDL_BUTTON_LEFT
&& event->clicks == 2) {
int32_t x = event->x;
int32_t y = event->y;
sc_screen_hidpi_scale_coords(im->screen, &x, &y);
SDL_Rect *r = &im->screen->rect;
bool outside = x < r->x || x >= r->x + r->w
|| y < r->y || y >= r->y + r->h;
if (outside) {
if (down) {
sc_screen_resize_to_fit(im->screen);
}
return;
}
// double-click on black borders resize to fit the device screen
bool video = im->screen->video;
if (video && event->button == SDL_BUTTON_LEFT && event->clicks == 2) {
int32_t x = event->x;
int32_t y = event->y;
sc_screen_hidpi_scale_coords(im->screen, &x, &y);
SDL_Rect *r = &im->screen->rect;
bool outside = x < r->x || x >= r->x + r->w
|| y < r->y || y >= r->y + r->h;
if (outside) {
if (down) {
sc_screen_resize_to_fit(im->screen);
}
return;
}
}
// otherwise, send the click event to the device
}
if (!im->mp || paused) {
return;
}
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;
uint32_t sdl_buttons_state = SDL_GetMouseState(NULL, NULL);
struct sc_mouse_click_event evt = {
.position = sc_input_manager_get_position(im, event->x, event->y),
.action = sc_action_from_sdl_mousebutton_type(event->type),
.button = sc_mouse_button_from_sdl(event->button),
.pointer_id = use_finger ? SC_POINTER_ID_GENERIC_FINGER
: SC_POINTER_ID_MOUSE,
.buttons_state = im->mouse_buttons_state,
.pointer_id = im->forward_all_clicks ? POINTER_ID_MOUSE
: POINTER_ID_GENERIC_FINGER,
.buttons_state =
sc_mouse_buttons_state_from_sdl(sdl_buttons_state,
im->forward_all_clicks),
};
assert(im->mp->ops->process_mouse_click);
@ -858,7 +806,14 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
// 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
// the screen.
if (change_vfinger) {
const SDL_Keymod keymod = SDL_GetModState();
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 =
sc_screen_convert_window_to_frame_coords(im->screen, event->x,
event->y);
@ -891,7 +846,6 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
int mouse_x;
int mouse_y;
uint32_t buttons = SDL_GetMouseState(&mouse_x, &mouse_y);
(void) buttons; // Actual buttons are tracked manually to ignore shortcuts
struct sc_mouse_scroll_event evt = {
.position = sc_input_manager_get_position(im, mouse_x, mouse_y),
@ -902,74 +856,13 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
.hscroll = CLAMP(event->x, -1, 1),
.vscroll = CLAMP(event->y, -1, 1),
#endif
.buttons_state = im->mouse_buttons_state,
.buttons_state =
sc_mouse_buttons_state_from_sdl(buttons, im->forward_all_clicks),
};
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) {
struct sc_gamepad_axis_event evt = {
.gamepad_id = event->which,
.axis = sc_gamepad_axis_from_sdl(event->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) {
struct sc_gamepad_button_event evt = {
.gamepad_id = event->which,
.action = sc_action_from_sdl_controllerbutton_type(event->type),
.button = sc_gamepad_button_from_sdl(event->button),
};
im->gp->ops->process_gamepad_button(im->gp, &evt);
}
static bool
is_apk(const char *file) {
const char *ext = strrchr(file, '.');
@ -1042,27 +935,6 @@ sc_input_manager_handle_event(struct sc_input_manager *im,
}
sc_input_manager_process_touch(im, &event->tfinger);
break;
case SDL_CONTROLLERDEVICEADDED:
case SDL_CONTROLLERDEVICEREMOVED:
// Handle device added or removed even if paused
if (!im->gp) {
break;
}
sc_input_manager_process_gamepad_device(im, &event->cdevice);
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: {
if (!control) {
break;

View File

@ -11,7 +11,6 @@
#include "file_pusher.h"
#include "fps_counter.h"
#include "options.h"
#include "trait/gamepad_processor.h"
#include "trait/key_processor.h"
#include "trait/mouse_processor.h"
@ -22,20 +21,20 @@ struct sc_input_manager {
struct sc_key_processor *kp;
struct sc_mouse_processor *mp;
struct sc_gamepad_processor *gp;
struct sc_mouse_bindings mouse_bindings;
bool forward_all_clicks;
bool legacy_paste;
bool clipboard_autosync;
uint16_t sdl_shortcut_mods;
struct {
unsigned data[SC_MAX_SHORTCUT_MODS];
unsigned count;
} sdl_shortcut_mods;
bool vfinger_down;
bool vfinger_invert_x;
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.
// Not to be confused with event->repeat, which counts the number of
// system-generated repeated key presses.
@ -52,12 +51,11 @@ struct sc_input_manager_params {
struct sc_screen *screen;
struct sc_key_processor *kp;
struct sc_mouse_processor *mp;
struct sc_gamepad_processor *gp;
struct sc_mouse_bindings mouse_bindings;
bool forward_all_clicks;
bool legacy_paste;
bool clipboard_autosync;
uint8_t shortcut_mods; // OR of enum sc_shortcut_mod values
const struct sc_shortcut_mods *shortcut_mods;
};
void

View File

@ -16,7 +16,6 @@
#include "usb/scrcpy_otg.h"
#include "util/log.h"
#include "util/net.h"
#include "util/thread.h"
#include "version.h"
#ifdef _WIN32
@ -68,9 +67,6 @@ main_scrcpy(int argc, char *argv[]) {
goto end;
}
// The current thread is the main thread
SC_MAIN_THREAD_ID = sc_thread_get_id();
#ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL
av_register_all();
#endif

View File

@ -58,18 +58,17 @@ convert_touch_action(enum sc_touch_action action) {
static void
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
const struct sc_mouse_motion_event *event) {
struct sc_mouse_sdk *m = DOWNCAST(mp);
if (!m->mouse_hover && !event->buttons_state) {
if (!event->buttons_state) {
// Do not send motion events when no click is pressed
return;
}
struct sc_mouse_sdk *m = DOWNCAST(mp);
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
.inject_touch_event = {
.action = event->buttons_state ? AMOTION_EVENT_ACTION_MOVE
: AMOTION_EVENT_ACTION_HOVER_MOVE,
.action = AMOTION_EVENT_ACTION_MOVE,
.pointer_id = event->pointer_id,
.position = event->position,
.pressure = 1.f,
@ -146,10 +145,8 @@ sc_mouse_processor_process_touch(struct sc_mouse_processor *mp,
}
void
sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller,
bool mouse_hover) {
sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller) {
m->controller = controller;
m->mouse_hover = mouse_hover;
static const struct sc_mouse_processor_ops ops = {
.process_mouse_motion = sc_mouse_processor_process_mouse_motion,

View File

@ -13,11 +13,9 @@ struct sc_mouse_sdk {
struct sc_mouse_processor mouse_processor; // mouse processor trait
struct sc_controller *controller;
bool mouse_hover;
};
void
sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller,
bool mouse_hover);
sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller);
#endif

View File

@ -23,21 +23,6 @@ const struct scrcpy_options scrcpy_options_default = {
.record_format = SC_RECORD_FORMAT_AUTO,
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_AUTO,
.mouse_input_mode = SC_MOUSE_INPUT_MODE_AUTO,
.gamepad_input_mode = SC_GAMEPAD_INPUT_MODE_AUTO,
.mouse_bindings = {
.pri = {
.right_click = SC_MOUSE_BINDING_AUTO,
.middle_click = 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,
.port_range = {
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST,
@ -45,7 +30,10 @@ const struct scrcpy_options scrcpy_options_default = {
},
.tunnel_host = 0,
.tunnel_port = 0,
.shortcut_mods = SC_SHORTCUT_MOD_LALT | SC_SHORTCUT_MOD_LSUPER,
.shortcut_mods = {
.data = {SC_SHORTCUT_MOD_LALT, SC_SHORTCUT_MOD_LSUPER},
.count = 2,
},
.max_size = 0,
.video_bit_rate = 0,
.audio_bit_rate = 0,
@ -83,6 +71,7 @@ const struct scrcpy_options scrcpy_options_default = {
.force_adb_forward = false,
.disable_screensaver = false,
.forward_key_repeat = true,
.forward_all_clicks = false,
.legacy_paste = false,
.power_off_on_close = false,
.clipboard_autosync = true,
@ -101,8 +90,6 @@ const struct scrcpy_options scrcpy_options_default = {
.camera_high_speed = false,
.list = 0,
.window = true,
.mouse_hover = true,
.audio_dup = false,
};
enum sc_orientation

View File

@ -59,7 +59,6 @@ enum sc_audio_source {
SC_AUDIO_SOURCE_AUTO, // OUTPUT for video DISPLAY, MIC for video CAMERA
SC_AUDIO_SOURCE_OUTPUT,
SC_AUDIO_SOURCE_MIC,
SC_AUDIO_SOURCE_PLAYBACK,
};
enum sc_camera_facing {
@ -142,7 +141,6 @@ enum sc_lock_video_orientation {
enum sc_keyboard_input_mode {
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_SDK,
SC_KEYBOARD_INPUT_MODE_UHID,
@ -151,43 +149,12 @@ enum sc_keyboard_input_mode {
enum sc_mouse_input_mode {
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_SDK,
SC_MOUSE_INPUT_MODE_UHID,
SC_MOUSE_INPUT_MODE_AOA,
};
enum sc_gamepad_input_mode {
SC_GAMEPAD_INPUT_MODE_AUTO,
SC_GAMEPAD_INPUT_MODE_UHID_OR_AOA, // normal vs otg mode
SC_GAMEPAD_INPUT_MODE_DISABLED,
SC_GAMEPAD_INPUT_MODE_UHID,
SC_GAMEPAD_INPUT_MODE_AOA,
};
enum sc_mouse_binding {
SC_MOUSE_BINDING_AUTO,
SC_MOUSE_BINDING_DISABLED,
SC_MOUSE_BINDING_CLICK,
SC_MOUSE_BINDING_BACK,
SC_MOUSE_BINDING_HOME,
SC_MOUSE_BINDING_APP_SWITCH,
SC_MOUSE_BINDING_EXPAND_NOTIFICATION_PANEL,
};
struct sc_mouse_binding_set {
enum sc_mouse_binding right_click;
enum sc_mouse_binding middle_click;
enum sc_mouse_binding click4;
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 {
// Inject special keys, letters and space as key events.
// Inject numbers and punctuation as text events.
@ -202,6 +169,8 @@ enum sc_key_inject_mode {
SC_KEY_INJECT_MODE_RAW,
};
#define SC_MAX_SHORTCUT_MODS 8
enum sc_shortcut_mod {
SC_SHORTCUT_MOD_LCTRL = 1 << 0,
SC_SHORTCUT_MOD_RCTRL = 1 << 1,
@ -211,6 +180,11 @@ enum sc_shortcut_mod {
SC_SHORTCUT_MOD_RSUPER = 1 << 5,
};
struct sc_shortcut_mods {
unsigned data[SC_MAX_SHORTCUT_MODS];
unsigned count;
};
struct sc_port_range {
uint16_t first;
uint16_t last;
@ -241,13 +215,11 @@ struct scrcpy_options {
enum sc_record_format record_format;
enum sc_keyboard_input_mode keyboard_input_mode;
enum sc_mouse_input_mode mouse_input_mode;
enum sc_gamepad_input_mode gamepad_input_mode;
struct sc_mouse_bindings mouse_bindings;
enum sc_camera_facing camera_facing;
struct sc_port_range port_range;
uint32_t tunnel_host;
uint16_t tunnel_port;
uint8_t shortcut_mods; // OR of enum sc_shortcut_mod values
struct sc_shortcut_mods shortcut_mods;
uint16_t max_size;
uint32_t video_bit_rate;
uint32_t audio_bit_rate;
@ -285,6 +257,7 @@ struct scrcpy_options {
bool force_adb_forward;
bool disable_screensaver;
bool forward_key_repeat;
bool forward_all_clicks;
bool legacy_paste;
bool power_off_on_close;
bool clipboard_autosync;
@ -307,8 +280,6 @@ struct scrcpy_options {
#define SC_OPTION_LIST_CAMERA_SIZES 0x8
uint8_t list;
bool window;
bool mouse_hover;
bool audio_dup;
};
extern const struct scrcpy_options scrcpy_options_default;

View File

@ -6,17 +6,9 @@
#include <SDL2/SDL_clipboard.h>
#include "device_msg.h"
#include "events.h"
#include "util/log.h"
#include "util/str.h"
struct sc_uhid_output_task_data {
struct sc_uhid_devices *uhid_devices;
uint16_t id;
uint16_t size;
uint8_t *data;
};
bool
sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket,
const struct sc_receiver_callbacks *cbs, void *cbs_userdata) {
@ -29,7 +21,7 @@ sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket,
receiver->acksync = NULL;
receiver->uhid_devices = NULL;
assert(cbs && cbs->on_ended);
assert(cbs && cbs->on_error);
receiver->cbs = cbs;
receiver->cbs_userdata = cbs_userdata;
@ -41,49 +33,20 @@ sc_receiver_destroy(struct sc_receiver *receiver) {
sc_mutex_destroy(&receiver->mutex);
}
static void
task_set_clipboard(void *userdata) {
char *text = userdata;
char *current = SDL_GetClipboardText();
bool same = current && !strcmp(current, text);
SDL_free(current);
if (same) {
LOGD("Computer clipboard unchanged");
free(text);
return;
}
LOGI("Device clipboard copied");
SDL_SetClipboardText(text);
free(text);
}
static void
task_uhid_output(void *userdata) {
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
process_msg(struct sc_receiver *receiver, struct sc_device_msg *msg) {
switch (msg->type) {
case DEVICE_MSG_TYPE_CLIPBOARD: {
// Take ownership of the text (do not destroy the msg)
char *text = msg->clipboard.text;
bool ok = sc_post_to_main_thread(task_set_clipboard, text);
if (!ok) {
LOGW("Could not post clipboard to main thread");
free(text);
char *current = SDL_GetClipboardText();
bool same = current && !strcmp(current, msg->clipboard.text);
SDL_free(current);
if (same) {
LOGD("Computer clipboard unchanged");
return;
}
LOGI("Device clipboard copied");
SDL_SetClipboardText(msg->clipboard.text);
break;
}
case DEVICE_MSG_TYPE_ACK_CLIPBOARD:
@ -101,7 +64,6 @@ process_msg(struct sc_receiver *receiver, struct sc_device_msg *msg) {
}
sc_acksync_ack(receiver->acksync, msg->ack_clipboard.sequence);
// No allocation to free in the msg
break;
case DEVICE_MSG_TYPE_UHID_OUTPUT:
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
@ -124,33 +86,19 @@ process_msg(struct sc_receiver *receiver, struct sc_device_msg *msg) {
// Also check at runtime (do not trust the server)
if (!receiver->uhid_devices) {
LOGE("Received unexpected HID output message");
sc_device_msg_destroy(msg);
return;
}
struct sc_uhid_output_task_data *data = malloc(sizeof(*data));
if (!data) {
LOG_OOM();
return;
struct sc_uhid_receiver *uhid_receiver =
sc_uhid_devices_get_receiver(receiver->uhid_devices,
msg->uhid_output.id);
if (uhid_receiver) {
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;
}
}
@ -169,7 +117,7 @@ process_msgs(struct sc_receiver *receiver, const uint8_t *buf, size_t len) {
}
process_msg(receiver, &msg);
// the device msg must be destroyed by process_msg()
sc_device_msg_destroy(&msg);
head += r;
assert(head <= len);
@ -186,15 +134,12 @@ run_receiver(void *data) {
static uint8_t buf[DEVICE_MSG_MAX_SIZE];
size_t head = 0;
bool error = false;
for (;;) {
assert(head < DEVICE_MSG_MAX_SIZE);
ssize_t r = net_recv(receiver->control_socket, buf + head,
DEVICE_MSG_MAX_SIZE - head);
if (r <= 0) {
LOGD("Receiver stopped");
// device disconnected: keep error=false
break;
}
@ -202,7 +147,6 @@ run_receiver(void *data) {
ssize_t consumed = process_msgs(receiver, buf, head);
if (consumed == -1) {
// an error occurred
error = true;
break;
}
@ -213,7 +157,7 @@ run_receiver(void *data) {
}
}
receiver->cbs->on_ended(receiver, error, receiver->cbs_userdata);
receiver->cbs->on_error(receiver, receiver->cbs_userdata);
return 0;
}

View File

@ -25,7 +25,7 @@ struct sc_receiver {
};
struct sc_receiver_callbacks {
void (*on_ended)(struct sc_receiver *receiver, bool error, void *userdata);
void (*on_error)(struct sc_receiver *receiver, void *userdata);
};
bool

View File

@ -25,12 +25,10 @@
#include "recorder.h"
#include "screen.h"
#include "server.h"
#include "uhid/gamepad_uhid.h"
#include "uhid/keyboard_uhid.h"
#include "uhid/mouse_uhid.h"
#ifdef HAVE_USB
# include "usb/aoa_hid.h"
# include "usb/gamepad_aoa.h"
# include "usb/keyboard_aoa.h"
# include "usb/mouse_aoa.h"
# include "usb/usb.h"
@ -79,21 +77,27 @@ struct scrcpy {
struct sc_mouse_uhid mouse_uhid;
#ifdef HAVE_USB
struct sc_mouse_aoa mouse_aoa;
#endif
};
union {
struct sc_gamepad_uhid gamepad_uhid;
#ifdef HAVE_USB
struct sc_gamepad_aoa gamepad_aoa;
#endif
};
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
static BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) {
if (ctrl_type == CTRL_C_EVENT) {
sc_push_event(SDL_QUIT);
PUSH_EVENT(SDL_QUIT);
return TRUE;
}
return FALSE;
@ -176,20 +180,12 @@ event_loop(struct scrcpy *s) {
case SC_EVENT_RECORDER_ERROR:
LOGE("Recorder error");
return SCRCPY_EXIT_FAILURE;
case SC_EVENT_AOA_OPEN_ERROR:
LOGE("AOA open error");
return SCRCPY_EXIT_FAILURE;
case SC_EVENT_TIME_LIMIT_REACHED:
LOGI("Time limit reached");
return SCRCPY_EXIT_SUCCESS;
case SDL_QUIT:
LOGD("User requested to quit");
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:
if (!sc_screen_handle_event(&s->screen, &event)) {
return SCRCPY_EXIT_FAILURE;
@ -200,21 +196,6 @@ event_loop(struct scrcpy *s) {
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
static bool
await_for_server(bool *connected) {
@ -249,7 +230,7 @@ sc_recorder_on_ended(struct sc_recorder *recorder, bool success,
(void) userdata;
if (!success) {
sc_push_event(SC_EVENT_RECORDER_ERROR);
PUSH_EVENT(SC_EVENT_RECORDER_ERROR);
}
}
@ -263,9 +244,9 @@ sc_video_demuxer_on_ended(struct sc_demuxer *demuxer,
assert(status != SC_DEMUXER_STATUS_DISABLED);
if (status == SC_DEMUXER_STATUS_EOS) {
sc_push_event(SC_EVENT_DEVICE_DISCONNECTED);
PUSH_EVENT(SC_EVENT_DEVICE_DISCONNECTED);
} else {
sc_push_event(SC_EVENT_DEMUXER_ERROR);
PUSH_EVENT(SC_EVENT_DEMUXER_ERROR);
}
}
@ -279,27 +260,22 @@ sc_audio_demuxer_on_ended(struct sc_demuxer *demuxer,
// Contrary to the video demuxer, keep mirroring if only the audio fails
// (unless --require-audio is set).
if (status == SC_DEMUXER_STATUS_EOS) {
sc_push_event(SC_EVENT_DEVICE_DISCONNECTED);
PUSH_EVENT(SC_EVENT_DEVICE_DISCONNECTED);
} else if (status == SC_DEMUXER_STATUS_ERROR
|| (status == SC_DEMUXER_STATUS_DISABLED
&& options->require_audio)) {
sc_push_event(SC_EVENT_DEMUXER_ERROR);
PUSH_EVENT(SC_EVENT_DEMUXER_ERROR);
}
}
static void
sc_controller_on_ended(struct sc_controller *controller, bool error,
void *userdata) {
sc_controller_on_error(struct sc_controller *controller, void *userdata) {
// Note: this function may be called twice, once from the controller thread
// and once from the receiver thread
(void) controller;
(void) userdata;
if (error) {
sc_push_event(SC_EVENT_CONTROLLER_ERROR);
} else {
sc_push_event(SC_EVENT_DEVICE_DISCONNECTED);
}
PUSH_EVENT(SC_EVENT_CONTROLLER_ERROR);
}
static void
@ -307,7 +283,7 @@ sc_server_on_connection_failed(struct sc_server *server, void *userdata) {
(void) server;
(void) userdata;
sc_push_event(SC_EVENT_SERVER_CONNECTION_FAILED);
PUSH_EVENT(SC_EVENT_SERVER_CONNECTION_FAILED);
}
static void
@ -315,7 +291,7 @@ sc_server_on_connected(struct sc_server *server, void *userdata) {
(void) server;
(void) userdata;
sc_push_event(SC_EVENT_SERVER_CONNECTED);
PUSH_EVENT(SC_EVENT_SERVER_CONNECTED);
}
static void
@ -333,7 +309,7 @@ sc_timeout_on_timeout(struct sc_timeout *timeout, void *userdata) {
(void) timeout;
(void) userdata;
sc_push_event(SC_EVENT_TIME_LIMIT_REACHED);
PUSH_EVENT(SC_EVENT_TIME_LIMIT_REACHED);
}
// Generate a scrcpy id to differentiate multiple running scrcpy instances
@ -345,21 +321,6 @@ scrcpy_generate_scid(void) {
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
scrcpy(struct scrcpy_options *options) {
static struct scrcpy scrcpy;
@ -392,7 +353,6 @@ scrcpy(struct scrcpy_options *options) {
bool aoa_hid_initialized = false;
bool keyboard_aoa_initialized = false;
bool mouse_aoa_initialized = false;
bool gamepad_aoa_initialized = false;
#endif
bool controller_initialized = false;
bool controller_started = false;
@ -401,6 +361,7 @@ scrcpy(struct scrcpy_options *options) {
bool timeout_started = false;
struct sc_acksync *acksync = NULL;
struct sc_uhid_devices *uhid_devices = NULL;
uint32_t scid = scrcpy_generate_scid();
@ -428,7 +389,6 @@ scrcpy(struct scrcpy_options *options) {
.display_id = options->display_id,
.video = options->video,
.audio = options->audio,
.audio_dup = options->audio_dup,
.show_touches = options->show_touches,
.stay_awake = options->stay_awake,
.video_codec_options = options->video_codec_options,
@ -507,13 +467,6 @@ 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);
// Await for server without blocking Ctrl+C handling
@ -611,11 +564,10 @@ scrcpy(struct scrcpy_options *options) {
struct sc_controller *controller = NULL;
struct sc_key_processor *kp = NULL;
struct sc_mouse_processor *mp = NULL;
struct sc_gamepad_processor *gp = NULL;
if (options->control) {
static const struct sc_controller_callbacks controller_cbs = {
.on_ended = sc_controller_on_ended,
.on_error = sc_controller_on_error,
};
if (!sc_controller_init(&s->controller, s->server.control_socket,
@ -631,9 +583,7 @@ scrcpy(struct scrcpy_options *options) {
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA;
bool use_mouse_aoa =
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA;
bool use_gamepad_aoa =
options->gamepad_input_mode == SC_GAMEPAD_INPUT_MODE_AOA;
if (use_keyboard_aoa || use_mouse_aoa || use_gamepad_aoa) {
if (use_keyboard_aoa || use_mouse_aoa) {
bool ok = sc_acksync_init(&s->acksync);
if (!ok) {
goto end;
@ -676,15 +626,12 @@ scrcpy(struct scrcpy_options *options) {
goto end;
}
bool aoa_fail = false;
if (use_keyboard_aoa) {
if (sc_keyboard_aoa_init(&s->keyboard_aoa, &s->aoa)) {
keyboard_aoa_initialized = true;
kp = &s->keyboard_aoa.key_processor;
} else {
LOGE("Could not initialize HID keyboard");
aoa_fail = true;
goto aoa_complete;
}
}
@ -694,19 +641,12 @@ scrcpy(struct scrcpy_options *options) {
mp = &s->mouse_aoa.mouse_processor;
} else {
LOGE("Could not initialized HID mouse");
aoa_fail = true;
goto aoa_complete;
}
}
if (use_gamepad_aoa) {
sc_gamepad_aoa_init(&s->gamepad_aoa, &s->aoa);
gp = &s->gamepad_aoa.gamepad_processor;
gamepad_aoa_initialized = true;
}
bool need_aoa = keyboard_aoa_initialized || mouse_aoa_initialized;
aoa_complete:
if (aoa_fail || !sc_aoa_start(&s->aoa)) {
if (!need_aoa || !sc_aoa_start(&s->aoa)) {
sc_acksync_destroy(&s->acksync);
sc_usb_disconnect(&s->usb);
sc_usb_destroy(&s->usb);
@ -723,9 +663,6 @@ aoa_complete:
assert(options->mouse_input_mode != SC_MOUSE_INPUT_MODE_AOA);
#endif
struct sc_keyboard_uhid *uhid_keyboard = NULL;
struct sc_gamepad_uhid *uhid_gamepad = NULL;
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_SDK) {
sc_keyboard_sdk_init(&s->keyboard_sdk, &s->controller,
options->key_inject_mode,
@ -733,17 +670,18 @@ aoa_complete:
kp = &s->keyboard_sdk.key_processor;
} else if (options->keyboard_input_mode
== SC_KEYBOARD_INPUT_MODE_UHID) {
bool ok = sc_keyboard_uhid_init(&s->keyboard_uhid, &s->controller);
sc_uhid_devices_init(&s->uhid_devices);
bool ok = sc_keyboard_uhid_init(&s->keyboard_uhid, &s->controller,
&s->uhid_devices);
if (!ok) {
goto end;
}
uhid_devices = &s->uhid_devices;
kp = &s->keyboard_uhid.key_processor;
uhid_keyboard = &s->keyboard_uhid;
}
if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK) {
sc_mouse_sdk_init(&s->mouse_sdk, &s->controller,
options->mouse_hover);
sc_mouse_sdk_init(&s->mouse_sdk, &s->controller);
mp = &s->mouse_sdk.mouse_processor;
} else if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_UHID) {
bool ok = sc_mouse_uhid_init(&s->mouse_uhid, &s->controller);
@ -753,17 +691,6 @@ aoa_complete:
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 || uhid_gamepad) {
sc_uhid_devices_init(&s->uhid_devices, uhid_keyboard, uhid_gamepad);
uhid_devices = &s->uhid_devices;
}
sc_controller_configure(&s->controller, acksync, uhid_devices);
if (!sc_controller_start(&s->controller)) {
@ -785,11 +712,10 @@ aoa_complete:
.fp = fp,
.kp = kp,
.mp = mp,
.gp = gp,
.mouse_bindings = options->mouse_bindings,
.forward_all_clicks = options->forward_all_clicks,
.legacy_paste = options->legacy_paste,
.clipboard_autosync = options->clipboard_autosync,
.shortcut_mods = options->shortcut_mods,
.shortcut_mods = &options->shortcut_mods,
.window_title = window_title,
.always_on_top = options->always_on_top,
.window_x = options->window_x,
@ -803,20 +729,23 @@ aoa_complete:
.start_fps_counter = options->start_fps_counter,
};
if (!sc_screen_init(&s->screen, &screen_params)) {
goto end;
}
screen_initialized = true;
struct sc_frame_source *src;
if (options->video_playback) {
struct sc_frame_source *src = &s->video_decoder.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;
}
}
if (!sc_screen_init(&s->screen, &screen_params)) {
goto end;
}
screen_initialized = true;
if (options->video_playback) {
sc_frame_source_add_sink(src, &s->screen.frame_sink);
}
}
@ -897,13 +826,7 @@ aoa_complete:
timeout_started = true;
}
bool use_gamepads = true;
if (use_gamepads) {
init_sdl_gamepads();
}
ret = event_loop(s);
terminate_event_loop();
LOGD("quit...");
if (options->video_playback) {
@ -928,9 +851,6 @@ end:
if (mouse_aoa_initialized) {
sc_mouse_aoa_destroy(&s->mouse_aoa);
}
if (gamepad_aoa_initialized) {
sc_gamepad_aoa_destroy(&s->gamepad_aoa);
}
sc_aoa_stop(&s->aoa);
sc_usb_stop(&s->usb);
}

View File

@ -306,9 +306,13 @@ sc_screen_frame_sink_open(struct sc_frame_sink *sink,
screen->frame_size.width = ctx->width;
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)
bool ok = sc_push_event(SC_EVENT_SCREEN_INIT_SIZE);
if (!ok) {
int ret = SDL_PushEvent(&event);
if (ret < 0) {
LOGW("Could not post init size event: %s", SDL_GetError());
return false;
}
@ -348,9 +352,13 @@ 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
// this new frame instead
} else {
static SDL_Event new_frame_event = {
.type = SC_EVENT_NEW_FRAME,
};
// Post the event on the UI thread
bool ok = sc_push_event(SC_EVENT_NEW_FRAME);
if (!ok) {
int ret = SDL_PushEvent(&new_frame_event);
if (ret < 0) {
LOGW("Could not post new frame event: %s", SDL_GetError());
return false;
}
@ -473,8 +481,7 @@ sc_screen_init(struct sc_screen *screen,
.screen = screen,
.kp = params->kp,
.mp = params->mp,
.gp = params->gp,
.mouse_bindings = params->mouse_bindings,
.forward_all_clicks = params->forward_all_clicks,
.legacy_paste = params->legacy_paste,
.clipboard_autosync = params->clipboard_autosync,
.shortcut_mods = params->shortcut_mods,

View File

@ -78,12 +78,11 @@ struct sc_screen_params {
struct sc_file_pusher *fp;
struct sc_key_processor *kp;
struct sc_mouse_processor *mp;
struct sc_gamepad_processor *gp;
struct sc_mouse_bindings mouse_bindings;
bool forward_all_clicks;
bool legacy_paste;
bool clipboard_autosync;
uint8_t shortcut_mods; // OR of enum sc_shortcut_mod values
const struct sc_shortcut_mods *shortcut_mods;
const char *window_title;
bool always_on_top;

View File

@ -147,7 +147,7 @@ log_level_to_server_string(enum sc_log_level level) {
return "error";
default:
assert(!"unexpected log level");
return NULL;
return "(unknown)";
}
}
@ -183,7 +183,6 @@ sc_server_get_codec_name(enum sc_codec codec) {
case SC_CODEC_RAW:
return "raw";
default:
assert(!"unexpected codec");
return NULL;
}
}
@ -198,22 +197,6 @@ sc_server_get_camera_facing_name(enum sc_camera_facing camera_facing) {
case SC_CAMERA_FACING_EXTERNAL:
return "external";
default:
assert(!"unexpected camera facing");
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;
}
}
@ -288,14 +271,8 @@ execute_server(struct sc_server *server,
assert(params->video_source == SC_VIDEO_SOURCE_CAMERA);
ADD_PARAM("video_source=camera");
}
// If audio is enabled, an "auto" audio source must have been resolved
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->audio_source == SC_AUDIO_SOURCE_MIC) {
ADD_PARAM("audio_source=mic");
}
if (params->max_size) {
ADD_PARAM("max_size=%" PRIu16, params->max_size);

View File

@ -50,7 +50,6 @@ struct sc_server_params {
uint32_t display_id;
bool video;
bool audio;
bool audio_dup;
bool show_touches;
bool stay_awake;
bool force_adb_forward;

View File

@ -1,50 +0,0 @@
#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

View File

@ -1,139 +0,0 @@
#include "gamepad_uhid.h"
#include "hid/hid_gamepad.h"
#include "input_events.h"
#include "util/log.h"
#include "util/str.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.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_process_hid_output(struct sc_gamepad_uhid *gamepad,
uint16_t hid_id, const uint8_t *data,
size_t size) {
(void) gamepad;
char *hex = sc_str_to_hex_string(data, size);
if (hex) {
LOGI("==== HID output [%" PRIu16 "] %s", hid_id, hex);
free(hex);
} else {
LOGI("==== HID output [%" PRIu16 "]", hid_id);
}
// TODO
}
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;
}

View File

@ -1,28 +0,0 @@
#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);
void
sc_gamepad_uhid_process_hid_output(struct sc_gamepad_uhid *gamepad,
uint16_t hid_id, const uint8_t *data,
size_t size);
#endif

View File

@ -9,19 +9,21 @@
#define DOWNCAST_RECEIVER(UR) \
container_of(UR, struct sc_keyboard_uhid, uhid_receiver)
#define UHID_KEYBOARD_ID 1
static void
sc_keyboard_uhid_send_input(struct sc_keyboard_uhid *kb,
const struct sc_hid_input *hid_input) {
const struct sc_hid_event *event) {
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_UHID_INPUT;
msg.uhid_input.id = hid_input->hid_id;
msg.uhid_input.id = UHID_KEYBOARD_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;
assert(event->size <= SC_HID_MAX_SIZE);
memcpy(msg.uhid_input.data, event->data, event->size);
msg.uhid_input.size = event->size;
if (!sc_controller_push_msg(kb->controller, &msg)) {
LOGE("Could not push UHID_INPUT message (key)");
LOGE("Could not send UHID_INPUT message (key)");
}
}
@ -29,22 +31,23 @@ static void
sc_keyboard_uhid_synchronize_mod(struct sc_keyboard_uhid *kb) {
SDL_Keymod sdl_mod = SDL_GetModState();
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) {
// Inherently racy (the HID output reports arrive asynchronously in
// response to key presses), but will re-synchronize on next key press
// or HID output anyway
kb->device_mod = mod;
atomic_store_explicit(&kb->device_mod, mod, memory_order_relaxed);
struct sc_hid_input hid_input;
if (!sc_hid_keyboard_generate_input_from_mods(&hid_input, diff)) {
return;
}
struct sc_hid_event hid_event;
sc_hid_keyboard_event_from_mods(&hid_event, diff);
LOGV("HID keyboard state synchronized");
sc_keyboard_uhid_send_input(kb, &hid_input);
sc_keyboard_uhid_send_input(kb, &hid_event);
}
}
@ -54,8 +57,6 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
uint64_t ack_to_wait) {
(void) ack_to_wait;
assert(sc_thread_get_id() == SC_MAIN_THREAD_ID);
if (event->repeat) {
// In USB HID protocol, key repeat is handled by the host (Android), so
// just ignore key repeat here.
@ -64,20 +65,22 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
struct sc_keyboard_uhid *kb = DOWNCAST(kp);
struct sc_hid_input hid_input;
struct sc_hid_event hid_event;
// Not all keys are supported, just ignore unsupported keys
if (sc_hid_keyboard_generate_input_from_key(&kb->hid, &hid_input, event)) {
if (sc_hid_keyboard_event_from_key(&kb->hid, &hid_event, event)) {
if (event->scancode == SC_SCANCODE_CAPSLOCK) {
kb->device_mod ^= SC_MOD_CAPS;
atomic_fetch_xor_explicit(&kb->device_mod, SC_MOD_CAPS,
memory_order_relaxed);
} else if (event->scancode == SC_SCANCODE_NUMLOCK) {
kb->device_mod ^= SC_MOD_NUM;
atomic_fetch_xor_explicit(&kb->device_mod, SC_MOD_NUM,
memory_order_relaxed);
} else {
// Synchronize modifiers (only if the scancode itself does not
// change the modifiers)
sc_keyboard_uhid_synchronize_mod(kb);
}
sc_keyboard_uhid_send_input(kb, &hid_input);
sc_keyboard_uhid_send_input(kb, &hid_event);
}
}
@ -95,31 +98,34 @@ sc_keyboard_uhid_to_sc_mod(uint8_t hid_led) {
return mod;
}
void
sc_keyboard_uhid_process_hid_output(struct sc_keyboard_uhid *kb,
const uint8_t *data, size_t size) {
assert(sc_thread_get_id() == SC_MAIN_THREAD_ID);
static void
sc_uhid_receiver_process_output(struct sc_uhid_receiver *receiver,
const uint8_t *data, size_t len) {
// Called from the thread receiving device messages
assert(size);
assert(len);
// Also check at runtime (do not trust the server)
if (!size) {
if (!len) {
LOGE("Unexpected empty HID output message");
return;
}
struct sc_keyboard_uhid *kb = DOWNCAST_RECEIVER(receiver);
uint8_t hid_led = data[0];
uint16_t device_mod = sc_keyboard_uhid_to_sc_mod(hid_led);
kb->device_mod = device_mod;
atomic_store_explicit(&kb->device_mod, device_mod, memory_order_relaxed);
}
bool
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);
kb->controller = controller;
kb->device_mod = 0;
atomic_init(&kb->device_mod, 0);
static const struct sc_key_processor_ops ops = {
.process_key = sc_key_processor_process_key,
@ -134,15 +140,19 @@ sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
kb->key_processor.hid = true;
kb->key_processor.ops = &ops;
struct sc_hid_open hid_open;
sc_hid_keyboard_generate_open(&hid_open);
assert(hid_open.hid_id == SC_HID_ID_KEYBOARD);
static const struct sc_uhid_receiver_ops uhid_receiver_ops = {
.process_output = sc_uhid_receiver_process_output,
};
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;
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
msg.uhid_create.id = SC_HID_ID_KEYBOARD;
msg.uhid_create.report_desc = hid_open.report_desc;
msg.uhid_create.report_desc_size = hid_open.report_desc_size;
msg.uhid_create.id = UHID_KEYBOARD_ID;
msg.uhid_create.report_desc = SC_HID_KEYBOARD_REPORT_DESC;
msg.uhid_create.report_desc_size = SC_HID_KEYBOARD_REPORT_DESC_LEN;
if (!sc_controller_push_msg(controller, &msg)) {
LOGE("Could not send UHID_CREATE message (keyboard)");
return false;

View File

@ -7,22 +7,21 @@
#include "controller.h"
#include "hid/hid_keyboard.h"
#include "uhid/uhid_output.h"
#include "trait/key_processor.h"
struct sc_keyboard_uhid {
struct sc_key_processor key_processor; // key processor trait
struct sc_uhid_receiver uhid_receiver;
struct sc_hid_keyboard hid;
struct sc_controller *controller;
uint16_t device_mod;
atomic_uint_least16_t device_mod;
};
bool
sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
struct sc_controller *controller);
void
sc_keyboard_uhid_process_hid_output(struct sc_keyboard_uhid *kb,
const uint8_t *data, size_t size);
struct sc_controller *controller,
struct sc_uhid_devices *uhid_devices);
#endif

View File

@ -7,20 +7,21 @@
/** Downcast mouse processor to mouse_uhid */
#define DOWNCAST(MP) container_of(MP, struct sc_mouse_uhid, mouse_processor)
#define UHID_MOUSE_ID 2
static void
sc_mouse_uhid_send_input(struct sc_mouse_uhid *mouse,
const struct sc_hid_input *hid_input,
const char *name) {
const struct sc_hid_event *event, const char *name) {
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_UHID_INPUT;
msg.uhid_input.id = hid_input->hid_id;
msg.uhid_input.id = UHID_MOUSE_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;
assert(event->size <= SC_HID_MAX_SIZE);
memcpy(msg.uhid_input.data, event->data, event->size);
msg.uhid_input.size = event->size;
if (!sc_controller_push_msg(mouse->controller, &msg)) {
LOGE("Could not push UHID_INPUT message (%s)", name);
LOGE("Could not send UHID_INPUT message (%s)", name);
}
}
@ -29,10 +30,10 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
const struct sc_mouse_motion_event *event) {
struct sc_mouse_uhid *mouse = DOWNCAST(mp);
struct sc_hid_input hid_input;
sc_hid_mouse_generate_input_from_motion(&hid_input, event);
struct sc_hid_event hid_event;
sc_hid_mouse_event_from_motion(&hid_event, event);
sc_mouse_uhid_send_input(mouse, &hid_input, "mouse motion");
sc_mouse_uhid_send_input(mouse, &hid_event, "mouse motion");
}
static void
@ -40,10 +41,10 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
const struct sc_mouse_click_event *event) {
struct sc_mouse_uhid *mouse = DOWNCAST(mp);
struct sc_hid_input hid_input;
sc_hid_mouse_generate_input_from_click(&hid_input, event);
struct sc_hid_event hid_event;
sc_hid_mouse_event_from_click(&hid_event, event);
sc_mouse_uhid_send_input(mouse, &hid_input, "mouse click");
sc_mouse_uhid_send_input(mouse, &hid_event, "mouse click");
}
static void
@ -51,10 +52,10 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
const struct sc_mouse_scroll_event *event) {
struct sc_mouse_uhid *mouse = DOWNCAST(mp);
struct sc_hid_input hid_input;
sc_hid_mouse_generate_input_from_scroll(&hid_input, event);
struct sc_hid_event hid_event;
sc_hid_mouse_event_from_scroll(&hid_event, event);
sc_mouse_uhid_send_input(mouse, &hid_input, "mouse scroll");
sc_mouse_uhid_send_input(mouse, &hid_event, "mouse scroll");
}
bool
@ -74,17 +75,13 @@ sc_mouse_uhid_init(struct sc_mouse_uhid *mouse,
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;
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
msg.uhid_create.id = SC_HID_ID_MOUSE;
msg.uhid_create.report_desc = hid_open.report_desc;
msg.uhid_create.report_desc_size = hid_open.report_desc_size;
msg.uhid_create.id = UHID_MOUSE_ID;
msg.uhid_create.report_desc = SC_HID_MOUSE_REPORT_DESC;
msg.uhid_create.report_desc_size = SC_HID_MOUSE_REPORT_DESC_LEN;
if (!sc_controller_push_msg(controller, &msg)) {
LOGE("Could not push UHID_CREATE message (mouse)");
LOGE("Could not send UHID_CREATE message (mouse)");
return false;
}

View File

@ -1,37 +1,25 @@
#include "uhid_output.h"
#include <assert.h>
#include <inttypes.h>
#include "uhid/keyboard_uhid.h"
#include "uhid/gamepad_uhid.h"
#include "util/log.h"
void
sc_uhid_devices_init(struct sc_uhid_devices *devices,
struct sc_keyboard_uhid *keyboard,
struct sc_gamepad_uhid *gamepad) {
devices->keyboard = keyboard;
devices->gamepad = gamepad;
sc_uhid_devices_init(struct sc_uhid_devices *devices) {
devices->count = 0;
}
void
sc_uhid_devices_process_hid_output(struct sc_uhid_devices *devices, uint16_t id,
const uint8_t *data, size_t size) {
if (id == SC_HID_ID_KEYBOARD) {
if (devices->keyboard) {
sc_keyboard_uhid_process_hid_output(devices->keyboard, data, size);
} else {
LOGW("Unexpected keyboard HID output without UHID keyboard");
sc_uhid_devices_add_receiver(struct sc_uhid_devices *devices,
struct sc_uhid_receiver *receiver) {
assert(devices->count < SC_UHID_MAX_RECEIVERS);
devices->receivers[devices->count++] = receiver;
}
struct sc_uhid_receiver *
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 if (id >= SC_HID_ID_GAMEPAD_FIRST && id <= SC_HID_ID_GAMEPAD_LAST) {
if (devices->gamepad) {
sc_gamepad_uhid_process_hid_output(devices->gamepad, id, data,
size);
} else {
LOGW("Unexpected gamepad HID output without UHID gamepad");
}
} else {
LOGW("HID output ignored for id %" PRIu16, id);
}
return NULL;
}

View File

@ -9,21 +9,37 @@
/**
* The communication with UHID devices is bidirectional.
*
* This component dispatches HID outputs to the expected processor.
* This component manages the registration of receivers to handle UHID output
* 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_keyboard_uhid *keyboard;
struct sc_gamepad_uhid *gamepad;
struct sc_uhid_receiver *receivers[SC_UHID_MAX_RECEIVERS];
unsigned count;
};
void
sc_uhid_devices_init(struct sc_uhid_devices *devices,
struct sc_keyboard_uhid *keyboard,
struct sc_gamepad_uhid *gamepad);
sc_uhid_devices_init(struct sc_uhid_devices *devices);
void
sc_uhid_devices_process_hid_output(struct sc_uhid_devices *devices, uint16_t id,
const uint8_t *data, size_t size);
sc_uhid_devices_add_receiver(struct sc_uhid_devices *devices,
struct sc_uhid_receiver *receiver);
struct sc_uhid_receiver *
sc_uhid_devices_get_receiver(struct sc_uhid_devices *devices, uint16_t id);
#endif

View File

@ -1,14 +1,11 @@
#include "util/log.h"
#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include "aoa_hid.h"
#include "events.h"
#include "util/log.h"
#include "util/str.h"
#include "util/vector.h"
// See <https://source.android.com/devices/accessories/aoa2#hid-support>.
#define ACCESSORY_REGISTER_HID 54
@ -18,49 +15,26 @@
#define DEFAULT_TIMEOUT 1000
// Drop droppable events above this limit
#define SC_AOA_EVENT_QUEUE_LIMIT 60
struct sc_vec_hid_ids SC_VECTOR(uint16_t);
#define SC_AOA_EVENT_QUEUE_MAX 64
static void
sc_hid_input_log(const struct sc_hid_input *hid_input) {
// HID input: [00] FF FF FF FF...
assert(hid_input->size);
char *hex = sc_str_to_hex_string(hid_input->data, hid_input->size);
sc_hid_event_log(uint16_t accessory_id, const struct sc_hid_event *event) {
// HID Event: [00] FF FF FF FF...
assert(event->size);
char *hex = sc_str_to_hex_string(event->data, event->size);
if (!hex) {
return;
}
LOGV("HID input: [%" PRIu16 "] %s", hid_input->hid_id, hex);
LOGV("HID Event: [%d] %s", accessory_id, 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("HD close: [%" PRIu16 "]", hid_close->hid_id);
}
bool
sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb,
struct sc_acksync *acksync) {
sc_vecdeque_init(&aoa->queue);
// Add 4 to support 4 non-droppable events without re-allocation
if (!sc_vecdeque_reserve(&aoa->queue, SC_AOA_EVENT_QUEUE_LIMIT + 4)) {
if (!sc_vecdeque_reserve(&aoa->queue, SC_AOA_EVENT_QUEUE_MAX)) {
return false;
}
@ -151,18 +125,38 @@ sc_aoa_set_hid_report_desc(struct sc_aoa *aoa, uint16_t accessory_id,
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
sc_aoa_send_hid_event(struct sc_aoa *aoa,
const struct sc_hid_input *hid_input) {
sc_aoa_send_hid_event(struct sc_aoa *aoa, uint16_t accessory_id,
const struct sc_hid_event *event) {
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
uint8_t request = ACCESSORY_SEND_HID_EVENT;
// <https://source.android.com/devices/accessories/aoa2.html#hid-support>
// value (arg0): accessory assigned ID for the HID device
// index (arg1): 0 (unused)
uint16_t value = hid_input->hid_id;
uint16_t value = accessory_id;
uint16_t index = 0;
unsigned char *data = (uint8_t *) hid_input->data; // discard const
uint16_t length = hid_input->size;
unsigned char *data = (uint8_t *) event->data; // discard const
uint16_t length = event->size;
int result = libusb_control_transfer(aoa->usb->handle, request_type,
request, value, index, data, length,
DEFAULT_TIMEOUT);
@ -175,7 +169,7 @@ sc_aoa_send_hid_event(struct sc_aoa *aoa,
return true;
}
static bool
bool
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 = ACCESSORY_UNREGISTER_HID;
@ -198,32 +192,13 @@ sc_aoa_unregister_hid(struct sc_aoa *aoa, uint16_t accessory_id) {
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
sc_aoa_push_input_with_ack_to_wait(struct sc_aoa *aoa,
const struct sc_hid_input *hid_input,
uint64_t ack_to_wait) {
sc_aoa_push_hid_event_with_ack_to_wait(struct sc_aoa *aoa,
uint16_t accessory_id,
const struct sc_hid_event *event,
uint64_t ack_to_wait) {
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
sc_hid_input_log(hid_input);
sc_hid_event_log(accessory_id, event);
}
sc_mutex_lock(&aoa->mutex);
@ -233,9 +208,9 @@ sc_aoa_push_input_with_ack_to_wait(struct sc_aoa *aoa,
struct sc_aoa_event *aoa_event =
sc_vecdeque_push_hole_noresize(&aoa->queue);
aoa_event->type = SC_AOA_EVENT_TYPE_INPUT;
aoa_event->input.hid = *hid_input;
aoa_event->input.ack_to_wait = ack_to_wait;
aoa_event->hid = *event;
aoa_event->accessory_id = accessory_id;
aoa_event->ack_to_wait = ack_to_wait;
if (was_empty) {
sc_cond_signal(&aoa->event_cond);
@ -248,159 +223,10 @@ sc_aoa_push_input_with_ack_to_wait(struct sc_aoa *aoa,
return !full;
}
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);
// 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_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
run_aoa_thread(void *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 (;;) {
sc_mutex_lock(&aoa->mutex);
while (!aoa->stopped && sc_vecdeque_is_empty(&aoa->queue)) {
@ -414,26 +240,36 @@ run_aoa_thread(void *data) {
assert(!sc_vecdeque_is_empty(&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);
bool cont = sc_aoa_process_event(aoa, &event, &vec_open);
if (!cont) {
// stopped
break;
}
}
if (ack_to_wait != SC_SEQUENCE_INVALID) {
LOGD("Waiting ack from server sequence=%" PRIu64_, ack_to_wait);
// 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 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;
} 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 close AOA device: %" PRIu16, hid_id);
LOGW("Could not send HID event to USB device");
}
}
sc_vector_destroy(&vec_open);
return 0;
}

View File

@ -13,27 +13,12 @@
#include "util/tick.h"
#include "util/vecdeque.h"
enum sc_aoa_event_type {
SC_AOA_EVENT_TYPE_OPEN,
SC_AOA_EVENT_TYPE_INPUT,
SC_AOA_EVENT_TYPE_CLOSE,
};
#define SC_HID_MAX_SIZE 8
struct sc_aoa_event {
enum sc_aoa_event_type type;
union {
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_hid_event hid;
uint16_t accessory_id;
uint64_t ack_to_wait;
};
struct sc_aoa_event_queue SC_VECDEQUE(struct sc_aoa_event);
@ -64,31 +49,24 @@ sc_aoa_stop(struct sc_aoa *aoa);
void
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
sc_aoa_push_open(struct sc_aoa *aoa, const struct sc_hid_open *hid_open,
bool exit_on_open_error);
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_push_close(struct sc_aoa *aoa, const struct sc_hid_close *hid_close);
sc_aoa_unregister_hid(struct sc_aoa *aoa, uint16_t accessory_id);
bool
sc_aoa_push_input_with_ack_to_wait(struct sc_aoa *aoa,
const struct sc_hid_input *hid_input,
uint64_t ack_to_wait);
sc_aoa_push_hid_event_with_ack_to_wait(struct sc_aoa *aoa,
uint16_t accessory_id,
const struct sc_hid_event *event,
uint64_t ack_to_wait);
static inline bool
sc_aoa_push_input(struct sc_aoa *aoa, const struct sc_hid_input *hid_input) {
return sc_aoa_push_input_with_ack_to_wait(aoa, hid_input,
SC_SEQUENCE_INVALID);
sc_aoa_push_hid_event(struct sc_aoa *aoa, uint16_t accessory_id,
const struct sc_hid_event *event) {
return sc_aoa_push_hid_event_with_ack_to_wait(aoa, accessory_id, event,
SC_SEQUENCE_INVALID);
}
#endif

View File

@ -1,91 +0,0 @@
#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
}

View File

@ -1,25 +0,0 @@
#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

View File

@ -8,16 +8,19 @@
/** Downcast key processor to keyboard_aoa */
#define DOWNCAST(KP) container_of(KP, struct sc_keyboard_aoa, key_processor)
#define HID_KEYBOARD_ACCESSORY_ID 1
static bool
push_mod_lock_state(struct sc_keyboard_aoa *kb, uint16_t mods_state) {
struct sc_hid_input hid_input;
if (!sc_hid_keyboard_generate_input_from_mods(&hid_input, mods_state)) {
struct sc_hid_event hid_event;
if (!sc_hid_keyboard_event_from_mods(&hid_event, mods_state)) {
// Nothing to do
return true;
}
if (!sc_aoa_push_input(kb->aoa, &hid_input)) {
LOGW("Could not push AOA HID input (mod lock state)");
if (!sc_aoa_push_hid_event(kb->aoa, HID_KEYBOARD_ACCESSORY_ID,
&hid_event)) {
LOGW("Could not request HID event (mod lock state)");
return false;
}
@ -38,10 +41,10 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
struct sc_keyboard_aoa *kb = DOWNCAST(kp);
struct sc_hid_input hid_input;
struct sc_hid_event hid_event;
// Not all keys are supported, just ignore unsupported keys
if (sc_hid_keyboard_generate_input_from_key(&kb->hid, &hid_input, event)) {
if (sc_hid_keyboard_event_from_key(&kb->hid, &hid_event, event)) {
if (!kb->mod_lock_synchronized) {
// Inject CAPSLOCK and/or NUMLOCK if necessary to synchronize
// keyboard state
@ -55,9 +58,11 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
// synchronization is acknowledged by the server, otherwise it could
// paste the old clipboard content.
if (!sc_aoa_push_input_with_ack_to_wait(kb->aoa, &hid_input,
ack_to_wait)) {
LOGW("Could not push AOA HID input (key)");
if (!sc_aoa_push_hid_event_with_ack_to_wait(kb->aoa,
HID_KEYBOARD_ACCESSORY_ID,
&hid_event,
ack_to_wait)) {
LOGW("Could not request HID event (key)");
}
}
}
@ -66,12 +71,11 @@ bool
sc_keyboard_aoa_init(struct sc_keyboard_aoa *kb, struct sc_aoa *aoa) {
kb->aoa = aoa;
struct sc_hid_open hid_open;
sc_hid_keyboard_generate_open(&hid_open);
bool ok = sc_aoa_push_open(aoa, &hid_open, true);
bool ok = sc_aoa_setup_hid(aoa, HID_KEYBOARD_ACCESSORY_ID,
SC_HID_KEYBOARD_REPORT_DESC,
SC_HID_KEYBOARD_REPORT_DESC_LEN);
if (!ok) {
LOGW("Could not push AOA HID open (keyboard)");
LOGW("Register HID keyboard failed");
return false;
}
@ -98,6 +102,9 @@ sc_keyboard_aoa_init(struct sc_keyboard_aoa *kb, struct sc_aoa *aoa) {
void
sc_keyboard_aoa_destroy(struct sc_keyboard_aoa *kb) {
(void) kb;
// Do nothing, kb->aoa will automatically unregister all devices
// Unregister HID keyboard so the soft keyboard shows again on Android
bool ok = sc_aoa_unregister_hid(kb->aoa, HID_KEYBOARD_ACCESSORY_ID);
if (!ok) {
LOGW("Could not unregister HID keyboard");
}
}

View File

@ -9,16 +9,19 @@
/** Downcast mouse processor to mouse_aoa */
#define DOWNCAST(MP) container_of(MP, struct sc_mouse_aoa, mouse_processor)
#define HID_MOUSE_ACCESSORY_ID 2
static void
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
const struct sc_mouse_motion_event *event) {
struct sc_mouse_aoa *mouse = DOWNCAST(mp);
struct sc_hid_input hid_input;
sc_hid_mouse_generate_input_from_motion(&hid_input, event);
struct sc_hid_event hid_event;
sc_hid_mouse_event_from_motion(&hid_event, event);
if (!sc_aoa_push_input(mouse->aoa, &hid_input)) {
LOGW("Could not push AOA HID input (mouse motion)");
if (!sc_aoa_push_hid_event(mouse->aoa, HID_MOUSE_ACCESSORY_ID,
&hid_event)) {
LOGW("Could not request HID event (mouse motion)");
}
}
@ -27,11 +30,12 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
const struct sc_mouse_click_event *event) {
struct sc_mouse_aoa *mouse = DOWNCAST(mp);
struct sc_hid_input hid_input;
sc_hid_mouse_generate_input_from_click(&hid_input, event);
struct sc_hid_event hid_event;
sc_hid_mouse_event_from_click(&hid_event, event);
if (!sc_aoa_push_input(mouse->aoa, &hid_input)) {
LOGW("Could not push AOA HID input (mouse click)");
if (!sc_aoa_push_hid_event(mouse->aoa, HID_MOUSE_ACCESSORY_ID,
&hid_event)) {
LOGW("Could not request HID event (mouse click)");
}
}
@ -40,11 +44,12 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
const struct sc_mouse_scroll_event *event) {
struct sc_mouse_aoa *mouse = DOWNCAST(mp);
struct sc_hid_input hid_input;
sc_hid_mouse_generate_input_from_scroll(&hid_input, event);
struct sc_hid_event hid_event;
sc_hid_mouse_event_from_scroll(&hid_event, event);
if (!sc_aoa_push_input(mouse->aoa, &hid_input)) {
LOGW("Could not push AOA HID input (mouse scroll)");
if (!sc_aoa_push_hid_event(mouse->aoa, HID_MOUSE_ACCESSORY_ID,
&hid_event)) {
LOGW("Could not request HID event (mouse scroll)");
}
}
@ -52,12 +57,11 @@ bool
sc_mouse_aoa_init(struct sc_mouse_aoa *mouse, struct sc_aoa *aoa) {
mouse->aoa = aoa;
struct sc_hid_open hid_open;
sc_hid_mouse_generate_open(&hid_open);
bool ok = sc_aoa_push_open(aoa, &hid_open, true);
bool ok = sc_aoa_setup_hid(aoa, HID_MOUSE_ACCESSORY_ID,
SC_HID_MOUSE_REPORT_DESC,
SC_HID_MOUSE_REPORT_DESC_LEN);
if (!ok) {
LOGW("Could not push AOA HID open (mouse)");
LOGW("Register HID mouse failed");
return false;
}
@ -78,6 +82,8 @@ sc_mouse_aoa_init(struct sc_mouse_aoa *mouse, struct sc_aoa *aoa) {
void
sc_mouse_aoa_destroy(struct sc_mouse_aoa *mouse) {
(void) mouse;
// Do nothing, mouse->aoa will automatically unregister all devices
bool ok = sc_aoa_unregister_hid(mouse->aoa, HID_MOUSE_ACCESSORY_ID);
if (!ok) {
LOGW("Could not unregister HID mouse");
}
}

View File

@ -12,7 +12,6 @@ struct scrcpy_otg {
struct sc_aoa aoa;
struct sc_keyboard_aoa keyboard;
struct sc_mouse_aoa mouse;
struct sc_gamepad_aoa gamepad;
struct sc_screen_otg screen_otg;
};
@ -22,8 +21,10 @@ sc_usb_on_disconnected(struct sc_usb *usb, void *userdata) {
(void) usb;
(void) userdata;
bool ok = sc_push_event(SC_EVENT_USB_DEVICE_DISCONNECTED);
if (!ok) {
SDL_Event event;
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());
}
}
@ -36,9 +37,6 @@ event_loop(struct scrcpy_otg *s) {
case SC_EVENT_USB_DEVICE_DISCONNECTED:
LOGW("Device disconnected");
return SCRCPY_EXIT_DISCONNECTED;
case SC_EVENT_AOA_OPEN_ERROR:
LOGE("AOA open error");
return SCRCPY_EXIT_FAILURE;
case SDL_QUIT:
LOGD("User requested to quit");
return SCRCPY_EXIT_SUCCESS;
@ -67,13 +65,6 @@ scrcpy_otg(struct scrcpy_options *options) {
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);
if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) {
@ -84,7 +75,6 @@ scrcpy_otg(struct scrcpy_options *options) {
struct sc_keyboard_aoa *keyboard = NULL;
struct sc_mouse_aoa *mouse = NULL;
struct sc_gamepad_aoa *gamepad = NULL;
bool usb_device_initialized = false;
bool usb_connected = false;
bool aoa_started = false;
@ -131,15 +121,11 @@ scrcpy_otg(struct scrcpy_options *options) {
|| options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_DISABLED);
assert(options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA
|| 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 =
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA;
bool enable_mouse =
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA;
bool enable_gamepad =
options->gamepad_input_mode == SC_GAMEPAD_INPUT_MODE_AOA;
if (enable_keyboard) {
ok = sc_keyboard_aoa_init(&s->keyboard, &s->aoa);
@ -157,11 +143,6 @@ scrcpy_otg(struct scrcpy_options *options) {
mouse = &s->mouse;
}
if (enable_gamepad) {
sc_gamepad_aoa_init(&s->gamepad, &s->aoa);
gamepad = &s->gamepad;
}
ok = sc_aoa_start(&s->aoa);
if (!ok) {
goto end;
@ -176,7 +157,6 @@ scrcpy_otg(struct scrcpy_options *options) {
struct sc_screen_otg_params params = {
.keyboard = keyboard,
.mouse = mouse,
.gamepad = gamepad,
.window_title = window_title,
.always_on_top = options->always_on_top,
.window_x = options->window_x,
@ -210,9 +190,6 @@ end:
if (keyboard) {
sc_keyboard_aoa_destroy(&s->keyboard);
}
if (gamepad) {
sc_gamepad_aoa_destroy(&s->gamepad);
}
if (aoa_initialized) {
sc_aoa_join(&s->aoa);

View File

@ -59,7 +59,6 @@ sc_screen_otg_init(struct sc_screen_otg *screen,
const struct sc_screen_otg_params *params) {
screen->keyboard = params->keyboard;
screen->mouse = params->mouse;
screen->gamepad = params->gamepad;
screen->mouse_capture_key_pressed = 0;
@ -170,7 +169,7 @@ sc_screen_otg_process_mouse_motion(struct sc_screen_otg *screen,
// .position not used for HID events
.xrel = event->xrel,
.yrel = event->yrel,
.buttons_state = sc_mouse_buttons_state_from_sdl(event->state),
.buttons_state = sc_mouse_buttons_state_from_sdl(event->state, true),
};
assert(mp->ops->process_mouse_motion);
@ -189,7 +188,8 @@ sc_screen_otg_process_mouse_button(struct sc_screen_otg *screen,
// .position not used for HID events
.action = sc_action_from_sdl_mousebutton_type(event->type),
.button = sc_mouse_button_from_sdl(event->button),
.buttons_state = sc_mouse_buttons_state_from_sdl(sdl_buttons_state),
.buttons_state =
sc_mouse_buttons_state_from_sdl(sdl_buttons_state, true),
};
assert(mp->ops->process_mouse_click);
@ -208,84 +208,14 @@ sc_screen_otg_process_mouse_wheel(struct sc_screen_otg *screen,
// .position not used for HID events
.hscroll = event->x,
.vscroll = event->y,
.buttons_state = sc_mouse_buttons_state_from_sdl(sdl_buttons_state),
.buttons_state =
sc_mouse_buttons_state_from_sdl(sdl_buttons_state, true),
};
assert(mp->ops->process_mouse_scroll);
mp->ops->process_mouse_scroll(mp, &evt);
}
static void
sc_screen_otg_process_gamepad_device(struct sc_screen_otg *screen,
const SDL_ControllerDeviceEvent *event) {
assert(screen->gamepad);
struct sc_gamepad_processor *gp = &screen->gamepad->gamepad_processor;
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;
struct sc_gamepad_axis_event evt = {
.gamepad_id = event->which,
.axis = sc_gamepad_axis_from_sdl(event->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;
struct sc_gamepad_button_event evt = {
.gamepad_id = event->which,
.action = sc_action_from_sdl_controllerbutton_type(event->type),
.button = sc_gamepad_button_from_sdl(event->button),
};
gp->ops->process_gamepad_button(gp, &evt);
}
void
sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event) {
switch (event->type) {
@ -365,23 +295,5 @@ sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event) {
sc_screen_otg_process_mouse_wheel(screen, &event->wheel);
}
break;
case SDL_CONTROLLERDEVICEADDED:
case SDL_CONTROLLERDEVICEREMOVED:
// Handle device added or removed even if paused
if (screen->gamepad) {
sc_screen_otg_process_gamepad_device(screen, &event->cdevice);
}
break;
case SDL_CONTROLLERAXISMOTION:
if (screen->gamepad) {
sc_screen_otg_process_gamepad_axis(screen, &event->caxis);
}
break;
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
if (screen->gamepad) {
sc_screen_otg_process_gamepad_button(screen, &event->cbutton);
}
break;
}
}

View File

@ -8,12 +8,10 @@
#include "keyboard_aoa.h"
#include "mouse_aoa.h"
#include "gamepad_aoa.h"
struct sc_screen_otg {
struct sc_keyboard_aoa *keyboard;
struct sc_mouse_aoa *mouse;
struct sc_gamepad_aoa *gamepad;
SDL_Window *window;
SDL_Renderer *renderer;
@ -26,7 +24,6 @@ struct sc_screen_otg {
struct sc_screen_otg_params {
struct sc_keyboard_aoa *keyboard;
struct sc_mouse_aoa *mouse;
struct sc_gamepad_aoa *gamepad;
const char *window_title;
bool always_on_top;

View File

@ -13,12 +13,6 @@ sc_write16be(uint8_t *buf, uint16_t 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
sc_write32be(uint8_t *buf, uint32_t value) {
buf[0] = value >> 24;
@ -27,26 +21,12 @@ sc_write32be(uint8_t *buf, uint32_t 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
sc_write64be(uint8_t *buf, uint64_t value) {
sc_write32be(buf, value >> 32);
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, value >> 32);
}
static inline uint16_t
sc_read16be(const uint8_t *buf) {
return (buf[0] << 8) | buf[1];

View File

@ -6,8 +6,6 @@
#include "log.h"
sc_thread_id SC_MAIN_THREAD_ID;
bool
sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name,
void *userdata) {

View File

@ -39,8 +39,6 @@ typedef struct sc_cond {
SDL_cond *cond;
} sc_cond;
extern sc_thread_id SC_MAIN_THREAD_ID;
bool
sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name,
void *userdata);

View File

@ -240,7 +240,7 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs, const AVCodecContext *ctx) {
vs->frame = av_frame_alloc();
if (!vs->frame) {
LOG_OOM();
goto error_avcodec_free_context;
goto error_avcodec_close;
}
vs->packet = av_packet_alloc();
@ -268,6 +268,8 @@ error_av_packet_free:
av_packet_free(&vs->packet);
error_av_frame_free:
av_frame_free(&vs->frame);
error_avcodec_close:
avcodec_close(vs->encoder_ctx);
error_avcodec_free_context:
avcodec_free_context(&vs->encoder_ctx);
error_avio_close:
@ -295,6 +297,7 @@ sc_v4l2_sink_close(struct sc_v4l2_sink *vs) {
av_packet_free(&vs->packet);
av_frame_free(&vs->frame);
avcodec_close(vs->encoder_ctx);
avcodec_free_context(&vs->encoder_ctx);
avio_close(vs->format_ctx->pb);
avformat_free_context(vs->format_ctx);

View File

@ -124,22 +124,32 @@ static void test_options2(void) {
}
static void test_parse_shortcut_mods(void) {
uint8_t mods;
struct sc_shortcut_mods mods;
bool ok;
ok = sc_parse_shortcut_mods("lctrl", &mods);
assert(ok);
assert(mods == SC_SHORTCUT_MOD_LCTRL);
assert(mods.count == 1);
assert(mods.data[0] == SC_SHORTCUT_MOD_LCTRL);
ok = sc_parse_shortcut_mods("lctrl+lalt", &mods);
assert(ok);
assert(mods.count == 1);
assert(mods.data[0] == (SC_SHORTCUT_MOD_LCTRL | SC_SHORTCUT_MOD_LALT));
ok = sc_parse_shortcut_mods("rctrl,lalt", &mods);
assert(ok);
assert(mods == (SC_SHORTCUT_MOD_RCTRL | SC_SHORTCUT_MOD_LALT));
assert(mods.count == 2);
assert(mods.data[0] == SC_SHORTCUT_MOD_RCTRL);
assert(mods.data[1] == SC_SHORTCUT_MOD_LALT);
ok = sc_parse_shortcut_mods("lsuper,rsuper,lctrl", &mods);
ok = sc_parse_shortcut_mods("lsuper,rsuper+lalt,lctrl+rctrl+ralt", &mods);
assert(ok);
assert(mods == (SC_SHORTCUT_MOD_LSUPER
| SC_SHORTCUT_MOD_RSUPER
| SC_SHORTCUT_MOD_LCTRL));
assert(mods.count == 3);
assert(mods.data[0] == SC_SHORTCUT_MOD_LSUPER);
assert(mods.data[1] == (SC_SHORTCUT_MOD_RSUPER | SC_SHORTCUT_MOD_LALT));
assert(mods.data[2] == (SC_SHORTCUT_MOD_LCTRL | SC_SHORTCUT_MOD_RCTRL |
SC_SHORTCUT_MOD_RALT));
ok = sc_parse_shortcut_mods("", &mods);
assert(!ok);

View File

@ -370,25 +370,6 @@ static void test_serialize_uhid_input(void) {
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) {
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS,
@ -424,7 +405,6 @@ int main(int argc, char *argv[]) {
test_serialize_rotate_device();
test_serialize_uhid_create();
test_serialize_uhid_input();
test_serialize_uhid_destroy();
test_serialize_open_hard_keyboard();
return 0;
}

View File

@ -7,7 +7,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.3.0'
classpath 'com.android.tools.build:gradle:8.1.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View File

@ -42,7 +42,7 @@ scrcpy --no-window
# interrupt with Ctrl+C
```
Without video, the audio latency is typically not critical, so it might be
Without video, the audio latency is typically not criticial, so it might be
interesting to add [buffering](#buffering) to minimize glitches:
```
@ -66,30 +66,6 @@ the computer:
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

View File

@ -94,7 +94,7 @@ This is the preferred method (and the way the release is built).
From _Debian_, install _mingw_:
```bash
sudo apt install mingw-w64 mingw-w64-tools libz-mingw-w64-dev
sudo apt install mingw-w64 mingw-w64-tools
```
You also need the JDK to build the server:
@ -233,10 +233,10 @@ install` must be run as root)._
#### Option 2: Use prebuilt server
- [`scrcpy-server-v2.6.1`][direct-scrcpy-server]
<sub>SHA-256: `ca7ab50b2e25a0e5af7599c30383e365983fa5b808e65ce2e1c1bba5bfe8dc3b`</sub>
- [`scrcpy-server-v2.4`][direct-scrcpy-server]
<sub>SHA-256: `93c272b7438605c055e127f7444064ed78fa9ca49f81156777fd201e79ce7ba3`</sub>
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.6.1/scrcpy-server-v2.6.1
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.4/scrcpy-server-v2.4
Download the prebuilt server somewhere, and specify its path during the Meson
configuration:

View File

@ -106,6 +106,15 @@ only inverts _x_.
This only works for the default mouse mode (`--mouse=sdk`).
## Right-click and middle-click
By default, right-click triggers BACK (or POWER on) and middle-click triggers
HOME. To disable these shortcuts and forward the clicks to the device instead:
```bash
scrcpy --forward-all-clicks
```
## File drop
### Install APK

View File

@ -6,7 +6,7 @@
Scrcpy is packaged in several distributions and package managers:
- Debian/Ubuntu: ~~`apt install scrcpy`~~ _(obsolete version)_
- Debian/Ubuntu: `apt install scrcpy`
- Arch Linux: `pacman -S scrcpy`
- Fedora: `dnf copr enable zeno/scrcpy && dnf install scrcpy`
- Gentoo: `emerge scrcpy`

View File

@ -18,14 +18,6 @@ Note that on some devices, an additional option must be enabled in developer
options for this mouse mode to work. See
[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
@ -76,69 +68,3 @@ debugging disabled (see [OTG](otg.md)).
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_).
## Mouse bindings
By default, with SDK mouse:
- right-click triggers BACK (or POWER on)
- middle-click triggers HOME
- the 4th click triggers APP_SWITCH
- the 5th click expands the notification panel
The secondary clicks may be forwarded to the device instead by pressing the
<kbd>Shift</kbd> key (e.g. <kbd>Shift</kbd>+right-click injects a right click to
the device).
In AOA and UHID mouse modes, the default bindings are reversed: all clicks are
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:
```
.---- Shift + right click
SECONDARY |.--- Shift + middle click
BINDINGS ||.-- Shift + 4th click
|||.- Shift + 5th click
||||
vvvv
--mouse-bind=xxxx:xxxx
^^^^
||||
PRIMARY ||| `- 5th click
BINDINGS || `-- 4th click
| `--- middle click
`---- right click
```
Each character must be one of the following:
- `+`: forward the click to the device
- `-`: ignore the click
- `b`: trigger shortcut BACK (or turn screen on if off)
- `h`: trigger shortcut HOME
- `s`: trigger shortcut APP_SWITCH
- `n`: trigger shortcut "expand notification panel"
For example:
```bash
scrcpy --mouse-bind=bhsn:++++ # the default mode for SDK mouse
scrcpy --mouse-bind=++++:bhsn # the default mode for AOA and UHID
scrcpy --mouse-bind=++bh:++sn # forward right and middle clicks,
# 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
```

View File

@ -13,8 +13,8 @@ It can be changed using `--shortcut-mod`. Possible keys are `lctrl`, `rctrl`,
# use RCtrl for shortcuts
scrcpy --shortcut-mod=rctrl
# use either LCtrl or LSuper for shortcuts
scrcpy --shortcut-mod=lctrl,lsuper
# use either LCtrl+LAlt or LSuper for shortcuts
scrcpy --shortcut-mod=lctrl+lalt,lsuper
```
_<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._

View File

@ -4,18 +4,24 @@
Download the [latest release]:
- [`scrcpy-win64-v2.6.1.zip`][direct-win64] (64-bit)
<sub>SHA-256: `041fc3abf8578ddcead5a8c4a8be8960b7c4d45b21d3370ee2683605e86a728c`</sub>
- [`scrcpy-win32-v2.6.1.zip`][direct-win32] (32-bit)
<sub>SHA-256: `17a5d4d17230b4c90fad45af6395efda9aea287a03c04e6b4ecc9ceb8134ea04`</sub>
- [`scrcpy-win64-v2.4.zip`][direct-win64] (64-bit)
<sub>SHA-256: `9dc56f21bfa455352ec0c58b40feaf2fb02d67372910a4235e298ece286ff3a9`</sub>
- [`scrcpy-win32-v2.4.zip`][direct-win32] (32-bit)
<sub>SHA-256: `cf92acc45eef37c6ee2db819f92e420ced3bc50f1348dd57f7d6ca1fc80f6116`</sub>
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.6.1/scrcpy-win64-v2.6.1.zip
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.6.1/scrcpy-win32-v2.6.1.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.4/scrcpy-win64-v2.4.zip
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.4/scrcpy-win32-v2.4.zip
and extract it.
Alternatively, you could install it from packages manager, like [Chocolatey]:
Alternatively, you could install it from packages manager, like [Winget]:
```bash
winget install scrcpy
```
or [Chocolatey]:
```bash
choco install scrcpy

View File

@ -2,8 +2,8 @@
set -e
BUILDDIR=build-auto
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.6.1/scrcpy-server-v2.6.1
PREBUILT_SERVER_SHA256=ca7ab50b2e25a0e5af7599c30383e365983fa5b808e65ce2e1c1bba5bfe8dc3b
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.4/scrcpy-server-v2.4
PREBUILT_SERVER_SHA256=93c272b7438605c055e127f7444064ed78fa9ca49f81156777fd201e79ce7ba3
echo "[scrcpy] Downloading prebuilt server..."
wget "$PREBUILT_SERVER_URL" -O scrcpy-server

View File

@ -1,5 +1,5 @@
project('scrcpy', 'c',
version: '2.6.1',
version: '2.4',
meson_version: '>= 0.48',
default_options: [
'c_std=c11',

View File

@ -24,7 +24,7 @@ SERVER_BUILD_DIR := build-server
WIN32_BUILD_DIR := build-win32
WIN64_BUILD_DIR := build-win64
VERSION ?= $(shell git describe --tags --exclude='*install-release' --always)
VERSION := $(shell git describe --tags --always)
DIST := dist
WIN32_TARGET_DIR := scrcpy-win32-$(VERSION)

View File

@ -7,8 +7,8 @@ android {
applicationId "com.genymobile.scrcpy"
minSdkVersion 21
targetSdkVersion 34
versionCode 20601
versionName "2.6.1"
versionCode 20400
versionName "2.4"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {

View File

@ -12,7 +12,7 @@
set -e
SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=2.6.1
SCRCPY_VERSION_NAME=2.4
PLATFORM=${ANDROID_PLATFORM:-34}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-34.0.0}
@ -50,29 +50,14 @@ cd "$SERVER_DIR/src/main/aidl"
android/content/IOnPrimaryClipChangedListener.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..."
cd ../java
javac -bootclasspath "$ANDROID_JAR" \
-cp "$LAMBDA_JAR:$GEN_DIR" \
-d "$CLASSES_DIR" \
-source 1.8 -target 1.8 \
${SRC[@]}
com/genymobile/scrcpy/*.java \
com/genymobile/scrcpy/wrappers/*.java
echo "Dexing..."
cd "$CLASSES_DIR"
@ -83,7 +68,8 @@ then
"$BUILD_TOOLS_DIR/dx" --dex --output "$BUILD_DIR/classes.dex" \
android/view/*.class \
android/content/*.class \
${CLASSES[@]}
com/genymobile/scrcpy/*.class \
com/genymobile/scrcpy/wrappers/*.class
echo "Archiving..."
cd "$BUILD_DIR"
@ -95,7 +81,8 @@ else
--output "$BUILD_DIR/classes.zip" \
android/view/*.class \
android/content/*.class \
${CLASSES[@]}
com/genymobile/scrcpy/*.class \
com/genymobile/scrcpy/wrappers/*.class
cd "$BUILD_DIR"
mv classes.zip "$SERVER_BINARY"

View File

@ -1,48 +1,55 @@
package com.genymobile.scrcpy.audio;
package com.genymobile.scrcpy;
import com.genymobile.scrcpy.FakeContext;
import com.genymobile.scrcpy.Workarounds;
import com.genymobile.scrcpy.util.Ln;
import com.genymobile.scrcpy.wrappers.ServiceManager;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.ComponentName;
import android.content.Intent;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.AudioTimestamp;
import android.media.MediaCodec;
import android.media.MediaRecorder;
import android.os.Build;
import android.os.SystemClock;
import java.nio.ByteBuffer;
public class AudioDirectCapture implements AudioCapture {
public final class AudioCapture {
private static final int SAMPLE_RATE = AudioConfig.SAMPLE_RATE;
private static final int CHANNEL_CONFIG = AudioConfig.CHANNEL_CONFIG;
private static final int CHANNELS = AudioConfig.CHANNELS;
private static final int CHANNEL_MASK = AudioConfig.CHANNEL_MASK;
private static final int ENCODING = AudioConfig.ENCODING;
public static final int SAMPLE_RATE = 48000;
public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO;
public static final int CHANNELS = 2;
public static final int CHANNEL_MASK = AudioFormat.CHANNEL_IN_LEFT | AudioFormat.CHANNEL_IN_RIGHT;
public static final int ENCODING = AudioFormat.ENCODING_PCM_16BIT;
public static final int BYTES_PER_SAMPLE = 2;
// Never read more than 1024 samples, even if the buffer is bigger (that would increase latency).
// A lower value is useless, since the system captures audio samples by blocks of 1024 (so for example if we read by blocks of 256 samples, we
// receive 4 successive blocks without waiting, then we wait for the 4 next ones).
public static final int MAX_READ_SIZE = 1024 * CHANNELS * BYTES_PER_SAMPLE;
private static final long ONE_SAMPLE_US = (1000000 + SAMPLE_RATE - 1) / SAMPLE_RATE; // 1 sample in microseconds (used for fixing PTS)
private final int audioSource;
private AudioRecord recorder;
private AudioRecordReader reader;
public AudioDirectCapture(AudioSource audioSource) {
this.audioSource = getAudioSourceValue(audioSource);
private final AudioTimestamp timestamp = new AudioTimestamp();
private long previousRecorderTimestamp = -1;
private long previousPts = 0;
private long nextPts = 0;
public AudioCapture(AudioSource audioSource) {
this.audioSource = audioSource.value();
}
private static int getAudioSourceValue(AudioSource audioSource) {
switch (audioSource) {
case OUTPUT:
return MediaRecorder.AudioSource.REMOTE_SUBMIX;
case MIC:
return MediaRecorder.AudioSource.MIC;
default:
throw new IllegalArgumentException("Unsupported audio source: " + audioSource);
}
private static AudioFormat createAudioFormat() {
AudioFormat.Builder builder = new AudioFormat.Builder();
builder.setEncoding(ENCODING);
builder.setSampleRate(SAMPLE_RATE);
builder.setChannelMask(CHANNEL_CONFIG);
return builder.build();
}
@TargetApi(Build.VERSION_CODES.M)
@ -54,7 +61,7 @@ public class AudioDirectCapture implements AudioCapture {
builder.setContext(FakeContext.get());
}
builder.setAudioSource(audioSource);
builder.setAudioFormat(AudioConfig.createAudioFormat());
builder.setAudioFormat(createAudioFormat());
int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, ENCODING);
// This buffer size does not impact latency
builder.setBufferSizeInBytes(8 * minBufferSize);
@ -79,7 +86,7 @@ public class AudioDirectCapture implements AudioCapture {
ServiceManager.getActivityManager().forceStopPackage(FakeContext.PACKAGE_NAME);
}
private void tryStartRecording(int attempts, int delayMs) throws AudioCaptureException {
private void tryStartRecording(int attempts, int delayMs) throws AudioCaptureForegroundException {
while (attempts-- > 0) {
// Wait for activity to start
SystemClock.sleep(delayMs);
@ -91,7 +98,7 @@ public class AudioDirectCapture implements AudioCapture {
Ln.e("Failed to start audio capture");
Ln.e("On Android 11, audio capture must be started in the foreground, make sure that the device is unlocked when starting "
+ "scrcpy.");
throw new AudioCaptureException();
throw new AudioCaptureForegroundException();
} else {
Ln.d("Failed to start audio capture, retrying...");
}
@ -99,7 +106,7 @@ public class AudioDirectCapture implements AudioCapture {
}
}
private void startRecording() throws AudioCaptureException {
private void startRecording() {
try {
recorder = createAudioRecord(audioSource);
} catch (NullPointerException e) {
@ -109,19 +116,9 @@ public class AudioDirectCapture implements AudioCapture {
recorder = Workarounds.createAudioRecord(audioSource, SAMPLE_RATE, CHANNEL_CONFIG, CHANNELS, CHANNEL_MASK, ENCODING);
}
recorder.startRecording();
reader = new AudioRecordReader(recorder);
}
@Override
public void checkCompatibility() throws AudioCaptureException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
Ln.w("Audio disabled: it is not supported before Android 11");
throw new AudioCaptureException();
}
}
@Override
public void start() throws AudioCaptureException {
public void start() throws AudioCaptureForegroundException {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
startWorkaroundAndroid11();
try {
@ -134,7 +131,6 @@ public class AudioDirectCapture implements AudioCapture {
}
}
@Override
public void stop() {
if (recorder != null) {
// Will call .stop() if necessary, without throwing an IllegalStateException
@ -142,9 +138,42 @@ public class AudioDirectCapture implements AudioCapture {
}
}
@Override
@TargetApi(Build.VERSION_CODES.N)
public int read(ByteBuffer outDirectBuffer, MediaCodec.BufferInfo outBufferInfo) {
return reader.read(outDirectBuffer, outBufferInfo);
public int read(ByteBuffer directBuffer, MediaCodec.BufferInfo outBufferInfo) {
int r = recorder.read(directBuffer, MAX_READ_SIZE);
if (r <= 0) {
return r;
}
long pts;
int ret = recorder.getTimestamp(timestamp, AudioTimestamp.TIMEBASE_MONOTONIC);
if (ret == AudioRecord.SUCCESS && timestamp.nanoTime != previousRecorderTimestamp) {
pts = timestamp.nanoTime / 1000;
previousRecorderTimestamp = timestamp.nanoTime;
} else {
if (nextPts == 0) {
Ln.w("Could not get initial audio timestamp");
nextPts = System.nanoTime() / 1000;
}
// compute from previous timestamp and packet size
pts = nextPts;
}
long durationUs = r * 1000000L / (CHANNELS * BYTES_PER_SAMPLE * SAMPLE_RATE);
nextPts = pts + durationUs;
if (previousPts != 0 && pts < previousPts + ONE_SAMPLE_US) {
// Audio PTS may come from two sources:
// - recorder.getTimestamp() if the call works;
// - an estimation from the previous PTS and the packet size as a fallback.
//
// Therefore, the property that PTS are monotonically increasing is no guaranteed in corner cases, so enforce it.
pts = previousPts + ONE_SAMPLE_US;
}
previousPts = pts;
outBufferInfo.set(0, r, pts, 0);
return r;
}
}

View File

@ -0,0 +1,7 @@
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 {
}

View File

@ -1,6 +1,4 @@
package com.genymobile.scrcpy.audio;
import com.genymobile.scrcpy.util.Codec;
package com.genymobile.scrcpy;
import android.media.MediaFormat;

View File

@ -1,14 +1,4 @@
package com.genymobile.scrcpy.audio;
import com.genymobile.scrcpy.AsyncProcessor;
import com.genymobile.scrcpy.util.Codec;
import com.genymobile.scrcpy.util.CodecOption;
import com.genymobile.scrcpy.util.CodecUtils;
import com.genymobile.scrcpy.device.ConfigurationException;
import com.genymobile.scrcpy.util.IO;
import com.genymobile.scrcpy.util.Ln;
import com.genymobile.scrcpy.util.LogUtils;
import com.genymobile.scrcpy.device.Streamer;
package com.genymobile.scrcpy;
import android.annotation.TargetApi;
import android.media.MediaCodec;
@ -44,8 +34,8 @@ public final class AudioEncoder implements AsyncProcessor {
}
}
private static final int SAMPLE_RATE = AudioConfig.SAMPLE_RATE;
private static final int CHANNELS = AudioConfig.CHANNELS;
private static final int SAMPLE_RATE = AudioCapture.SAMPLE_RATE;
private static final int CHANNELS = AudioCapture.CHANNELS;
private final AudioCapture capture;
private final Streamer streamer;
@ -132,7 +122,7 @@ public final class AudioEncoder implements AsyncProcessor {
} catch (ConfigurationException e) {
// Do not print stack trace, a user-friendly error-message has already been logged
fatalError = true;
} catch (AudioCaptureException e) {
} catch (AudioCaptureForegroundException e) {
// Do not print stack trace, a user-friendly error-message has already been logged
} catch (IOException e) {
Ln.e("Audio encoding error", e);
@ -176,7 +166,7 @@ public final class AudioEncoder implements AsyncProcessor {
}
@TargetApi(Build.VERSION_CODES.M)
private void encode() throws IOException, ConfigurationException, AudioCaptureException {
public void encode() throws IOException, ConfigurationException, AudioCaptureForegroundException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
Ln.w("Audio disabled: it is not supported before Android 11");
streamer.writeDisableStream(false);
@ -187,8 +177,6 @@ public final class AudioEncoder implements AsyncProcessor {
boolean mediaCodecStarted = false;
try {
capture.checkCompatibility(); // throws an AudioCaptureException on error
Codec codec = streamer.getCodec();
mediaCodec = createMediaCodec(codec, encoderName);

View File

@ -1,9 +1,4 @@
package com.genymobile.scrcpy.audio;
import com.genymobile.scrcpy.AsyncProcessor;
import com.genymobile.scrcpy.util.IO;
import com.genymobile.scrcpy.util.Ln;
import com.genymobile.scrcpy.device.Streamer;
package com.genymobile.scrcpy;
import android.media.MediaCodec;
import android.os.Build;
@ -23,14 +18,14 @@ public final class AudioRawRecorder implements AsyncProcessor {
this.streamer = streamer;
}
private void record() throws IOException, AudioCaptureException {
private void record() throws IOException, AudioCaptureForegroundException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
Ln.w("Audio disabled: it is not supported before Android 11");
streamer.writeDisableStream(false);
return;
}
final ByteBuffer buffer = ByteBuffer.allocateDirect(AudioConfig.MAX_READ_SIZE);
final ByteBuffer buffer = ByteBuffer.allocateDirect(AudioCapture.MAX_READ_SIZE);
final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
try {
@ -69,7 +64,7 @@ public final class AudioRawRecorder implements AsyncProcessor {
boolean fatalError = false;
try {
record();
} catch (AudioCaptureException e) {
} catch (AudioCaptureForegroundException e) {
// Do not print stack trace, a user-friendly error-message has already been logged
} catch (Throwable t) {
Ln.e("Audio recording error", t);

View File

@ -0,0 +1,30 @@
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;
}
}

View File

@ -1,4 +1,4 @@
package com.genymobile.scrcpy.util;
package com.genymobile.scrcpy;
public final class Binary {
private Binary() {

View File

@ -1,4 +1,4 @@
package com.genymobile.scrcpy.video;
package com.genymobile.scrcpy;
public final class CameraAspectRatio {
private static final float SENSOR = -1;

View File

@ -1,8 +1,5 @@
package com.genymobile.scrcpy.video;
package com.genymobile.scrcpy;
import com.genymobile.scrcpy.util.HandlerExecutor;
import com.genymobile.scrcpy.util.Ln;
import com.genymobile.scrcpy.device.Size;
import com.genymobile.scrcpy.wrappers.ServiceManager;
import android.annotation.SuppressLint;

View File

@ -1,4 +1,4 @@
package com.genymobile.scrcpy.video;
package com.genymobile.scrcpy;
import android.annotation.SuppressLint;
import android.hardware.camera2.CameraCharacteristics;
@ -21,7 +21,7 @@ public enum CameraFacing {
return value;
}
public static CameraFacing findByName(String name) {
static CameraFacing findByName(String name) {
for (CameraFacing facing : CameraFacing.values()) {
if (name.equals(facing.name)) {
return facing;

View File

@ -1,10 +1,5 @@
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.IOException;
import java.io.OutputStream;

View File

@ -1,4 +1,4 @@
package com.genymobile.scrcpy.util;
package com.genymobile.scrcpy;
public interface Codec {

View File

@ -1,4 +1,4 @@
package com.genymobile.scrcpy.util;
package com.genymobile.scrcpy;
import java.util.ArrayList;
import java.util.List;

View File

@ -1,7 +1,4 @@
package com.genymobile.scrcpy.util;
import com.genymobile.scrcpy.audio.AudioCodec;
import com.genymobile.scrcpy.video.VideoCodec;
package com.genymobile.scrcpy;
import android.media.MediaCodecInfo;
import android.media.MediaCodecList;

View File

@ -1,4 +1,4 @@
package com.genymobile.scrcpy.util;
package com.genymobile.scrcpy;
import java.io.IOException;
import java.util.Arrays;

View File

@ -1,4 +1,4 @@
package com.genymobile.scrcpy.device;
package com.genymobile.scrcpy;
public class ConfigurationException extends Exception {
public ConfigurationException(String message) {

View File

@ -0,0 +1,33 @@
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);
}
}

Some files were not shown because too many files have changed in this diff Show More