mirror of
https://github.com/Genymobile/scrcpy.git
synced 2026-02-25 15:54:28 +01:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e68e865d65 | ||
|
|
5a9e8ec7ba | ||
|
|
df72f67b33 | ||
|
|
9db64f83a4 | ||
|
|
27edeeb248 | ||
|
|
b947f9c2c8 | ||
|
|
0b9c0b31d5 | ||
|
|
d7e8ee1719 | ||
|
|
12203a68e8 | ||
|
|
2fbbb2ac65 | ||
|
|
a6978cb059 | ||
|
|
6c77ec6505 | ||
|
|
667078d1a6 | ||
|
|
0d93e73833 | ||
|
|
efdb7edc2a | ||
|
|
61e59ca735 | ||
|
|
c2784379f2 | ||
|
|
282e4751a8 | ||
|
|
473ab9ff75 | ||
|
|
05100dc752 | ||
|
|
31189c80f4 | ||
|
|
d8ab741be0 | ||
|
|
1d001e643a | ||
|
|
e7f270c145 | ||
|
|
242669ea5f |
11
FAQ.md
11
FAQ.md
@@ -133,9 +133,9 @@ Try with another USB cable or plug it into another USB port. See [#281] and
|
||||
[#283]: https://github.com/Genymobile/scrcpy/issues/283
|
||||
|
||||
|
||||
## OTG issues on Windows
|
||||
## HID/OTG issues on Windows
|
||||
|
||||
On Windows, if `scrcpy --otg` (or `--keyboard=aoa`/`--mouse=aoa`) results in:
|
||||
On Windows, if `scrcpy --otg` (or `--hid-keyboard`/`--hid-mouse`) results in:
|
||||
|
||||
> ERROR: Could not find any USB device
|
||||
|
||||
@@ -170,13 +170,12 @@ The default text injection method is [limited to ASCII characters][text-input].
|
||||
A trick allows to also inject some [accented characters][accented-characters],
|
||||
but that's all. See [#37].
|
||||
|
||||
To avoid the problem, [change the keyboard mode to simulate a physical
|
||||
keyboard][hid].
|
||||
It is also possible to simulate a [physical keyboard][hid] (HID).
|
||||
|
||||
[text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode
|
||||
[accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters
|
||||
[#37]: https://github.com/Genymobile/scrcpy/issues/37
|
||||
[hid]: doc/keyboard.md#physical-keyboard-simulation
|
||||
[hid]: doc/hid-otg.md
|
||||
|
||||
|
||||
## Client issues
|
||||
@@ -223,7 +222,7 @@ java.lang.IllegalStateException
|
||||
at android.media.MediaCodec.native_dequeueOutputBuffer(Native Method)
|
||||
```
|
||||
|
||||
then try with another [encoder](doc/video.md#encoder).
|
||||
then try with another [encoder](doc/video.md#codec).
|
||||
|
||||
|
||||
## Translations
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -188,7 +188,7 @@
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright (C) 2018 Genymobile
|
||||
Copyright (C) 2018-2024 Romain Vimont
|
||||
Copyright (C) 2018-2023 Romain Vimont
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
57
README.md
57
README.md
@@ -1,8 +1,4 @@
|
||||
**This GitHub repo (<https://github.com/Genymobile/scrcpy>) is the only official
|
||||
source for the project. Do not download releases from random websites, even if
|
||||
their name contains `scrcpy`.**
|
||||
|
||||
# scrcpy (v2.4)
|
||||
# scrcpy (v2.3.1)
|
||||
|
||||
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
|
||||
|
||||
@@ -36,13 +32,10 @@ Its features include:
|
||||
- [configurable quality](doc/video.md)
|
||||
- [camera mirroring](doc/camera.md) (Android 12+)
|
||||
- [mirroring as a webcam (V4L2)](doc/v4l2.md) (Linux-only)
|
||||
- physical [keyboard][hid-keyboard] and [mouse][hid-mouse] simulation (HID)
|
||||
- [OTG mode](doc/otg.md)
|
||||
- [physical keyboard/mouse simulation (HID)](doc/hid-otg.md)
|
||||
- [OTG mode](doc/hid-otg.md#otg)
|
||||
- and more…
|
||||
|
||||
[hid-keyboard]: doc/keyboard.md#physical-keyboard-simulation
|
||||
[hid-mouse]: doc/mouse.md#physical-mouse-simulation
|
||||
|
||||
## Prerequisites
|
||||
|
||||
The Android device requires at least API 21 (Android 5.0).
|
||||
@@ -60,7 +53,8 @@ this option is set.
|
||||
|
||||
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
|
||||
|
||||
Note that USB debugging is not required to run scrcpy in [OTG mode](doc/otg.md).
|
||||
Note that USB debugging is not required to run scrcpy in [OTG
|
||||
mode](doc/hid-otg.md#otg).
|
||||
|
||||
|
||||
## Get the app
|
||||
@@ -70,41 +64,6 @@ Note that USB debugging is not required to run scrcpy in [OTG mode](doc/otg.md).
|
||||
- [macOS](doc/macos.md)
|
||||
|
||||
|
||||
## Usage examples
|
||||
|
||||
There are a lot of options, [documented](#user-documentation) in separate pages.
|
||||
Here are just some common examples.
|
||||
|
||||
- Capture the screen in H.265 (better quality), limit the size to 1920, limit
|
||||
the frame rate to 60fps, disable audio, and control the device by simulating
|
||||
a physical keyboard:
|
||||
|
||||
```bash
|
||||
scrcpy --video-codec=h265 --max-size=1920 --max-fps=60 --no-audio --keyboard=uhid
|
||||
scrcpy --video-codec=h265 -m1920 --max-fps=60 --no-audio -K # short version
|
||||
```
|
||||
|
||||
- Record the device camera in H.265 at 1920x1080 (and microphone) to an MP4
|
||||
file:
|
||||
|
||||
```bash
|
||||
scrcpy --video-source=camera --video-codec=h265 --camera-size=1920x1080 --record=file.mp4
|
||||
```
|
||||
|
||||
- Capture the device front camera and expose it as a webcam on the computer (on
|
||||
Linux):
|
||||
|
||||
```bash
|
||||
scrcpy --video-source=camera --camera-size=1920x1080 --camera-facing=front --v4l2-sink=/dev/video2 --no-playback
|
||||
```
|
||||
|
||||
- Control the device without mirroring by simulating a physical keyboard and
|
||||
mouse (USB debugging not required):
|
||||
|
||||
```bash
|
||||
scrcpy --otg
|
||||
```
|
||||
|
||||
## User documentation
|
||||
|
||||
The application provides a lot of features and configuration options. They are
|
||||
@@ -114,13 +73,11 @@ documented in the following pages:
|
||||
- [Video](doc/video.md)
|
||||
- [Audio](doc/audio.md)
|
||||
- [Control](doc/control.md)
|
||||
- [Keyboard](doc/keyboard.md)
|
||||
- [Mouse](doc/mouse.md)
|
||||
- [Device](doc/device.md)
|
||||
- [Window](doc/window.md)
|
||||
- [Recording](doc/recording.md)
|
||||
- [Tunnels](doc/tunnels.md)
|
||||
- [OTG](doc/otg.md)
|
||||
- [HID/OTG](doc/hid-otg.md)
|
||||
- [Camera](doc/camera.md)
|
||||
- [Video4Linux](doc/v4l2.md)
|
||||
- [Shortcuts](doc/shortcuts.md)
|
||||
@@ -173,7 +130,7 @@ work][donate]:
|
||||
## Licence
|
||||
|
||||
Copyright (C) 2018 Genymobile
|
||||
Copyright (C) 2018-2024 Romain Vimont
|
||||
Copyright (C) 2018-2023 Romain Vimont
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -25,9 +25,10 @@ _scrcpy() {
|
||||
-e --select-tcpip
|
||||
-f --fullscreen
|
||||
--force-adb-forward
|
||||
--forward-all-clicks
|
||||
-h --help
|
||||
-K
|
||||
--keyboard=
|
||||
--keyboard
|
||||
--kill-adb-on-close
|
||||
--legacy-paste
|
||||
--list-camera-sizes
|
||||
@@ -40,7 +41,6 @@ _scrcpy() {
|
||||
-M
|
||||
--max-fps=
|
||||
--mouse=
|
||||
--mouse-bind=
|
||||
-n --no-control
|
||||
-N --no-playback
|
||||
--no-audio
|
||||
@@ -50,7 +50,6 @@ _scrcpy() {
|
||||
--no-downsize-on-error
|
||||
--no-key-repeat
|
||||
--no-mipmaps
|
||||
--no-mouse-hover
|
||||
--no-power-on
|
||||
--no-video
|
||||
--no-video-playback
|
||||
@@ -119,11 +118,11 @@ _scrcpy() {
|
||||
return
|
||||
;;
|
||||
--keyboard)
|
||||
COMPREPLY=($(compgen -W 'disabled sdk uhid aoa' -- "$cur"))
|
||||
COMPREPLY=($(compgen -W 'disabled sdk aoa uhid' -- "$cur"))
|
||||
return
|
||||
;;
|
||||
--mouse)
|
||||
COMPREPLY=($(compgen -W 'disabled sdk uhid aoa' -- "$cur"))
|
||||
COMPREPLY=($(compgen -W 'disabled sdk aoa uhid' -- "$cur"))
|
||||
return
|
||||
;;
|
||||
--orientation|--display-orientation)
|
||||
|
||||
@@ -32,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 aoa uhid)'
|
||||
'--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]'
|
||||
@@ -45,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 aoa uhid)'
|
||||
{-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]'
|
||||
@@ -56,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]'
|
||||
|
||||
1
app/deps/.gitignore
vendored
1
app/deps/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
/work
|
||||
@@ -1,27 +0,0 @@
|
||||
This directory (app/deps/) contains:
|
||||
|
||||
*.sh : shell scripts to download and build dependencies
|
||||
|
||||
patches/ : patches to fix dependencies (used by scripts)
|
||||
|
||||
work/sources/ : downloaded tarballs and extracted folders
|
||||
ffmpeg-6.1.1.tar.xz
|
||||
ffmpeg-6.1.1/
|
||||
libusb-1.0.27.tar.gz
|
||||
libusb-1.0.27/
|
||||
...
|
||||
work/build/ : build dirs for each dependency/version/architecture
|
||||
ffmpeg-6.1.1/win32/
|
||||
ffmpeg-6.1.1/win64/
|
||||
libusb-1.0.27/win32/
|
||||
libusb-1.0.27/win64/
|
||||
...
|
||||
work/install/ : install dirs for each architexture
|
||||
win32/bin/
|
||||
win32/include/
|
||||
win32/lib/
|
||||
win32/share/
|
||||
win64/bin/
|
||||
win64/include/
|
||||
win64/lib/
|
||||
win64/share/
|
||||
@@ -1,32 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
|
||||
cd "$DEPS_DIR"
|
||||
. common
|
||||
|
||||
VERSION=34.0.5
|
||||
FILENAME=platform-tools_r$VERSION-windows.zip
|
||||
PROJECT_DIR=platform-tools-$VERSION
|
||||
SHA256SUM=3f8320152704377de150418a3c4c9d07d16d80a6c0d0d8f7289c22c499e33571
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
if [[ -d "$PROJECT_DIR" ]]
|
||||
then
|
||||
echo "$PWD/$PROJECT_DIR" found
|
||||
else
|
||||
get_file "https://dl.google.com/android/repository/$FILENAME" "$FILENAME" "$SHA256SUM"
|
||||
mkdir -p "$PROJECT_DIR"
|
||||
cd "$PROJECT_DIR"
|
||||
ZIP_PREFIX=platform-tools
|
||||
unzip "../$FILENAME" \
|
||||
"$ZIP_PREFIX"/AdbWinApi.dll \
|
||||
"$ZIP_PREFIX"/AdbWinUsbApi.dll \
|
||||
"$ZIP_PREFIX"/adb.exe
|
||||
mv "$ZIP_PREFIX"/* .
|
||||
rmdir "$ZIP_PREFIX"
|
||||
fi
|
||||
|
||||
mkdir -p "$INSTALL_DIR/$HOST/bin"
|
||||
cd "$INSTALL_DIR/$HOST/bin"
|
||||
cp -r "$SOURCES_DIR/$PROJECT_DIR"/. "$INSTALL_DIR/$HOST/bin/"
|
||||
@@ -1,55 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# This file is intended to be sourced by other scripts, not executed
|
||||
|
||||
if [[ $# != 1 ]]
|
||||
then
|
||||
# <host>: win32 or win64
|
||||
echo "Syntax: $0 <host>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
HOST="$1"
|
||||
|
||||
if [[ "$HOST" = win32 ]]
|
||||
then
|
||||
HOST_TRIPLET=i686-w64-mingw32
|
||||
elif [[ "$HOST" = win64 ]]
|
||||
then
|
||||
HOST_TRIPLET=x86_64-w64-mingw32
|
||||
else
|
||||
echo "Unsupported host: $HOST" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
|
||||
cd "$DEPS_DIR"
|
||||
|
||||
PATCHES_DIR="$PWD/patches"
|
||||
|
||||
WORK_DIR="$PWD/work"
|
||||
SOURCES_DIR="$WORK_DIR/sources"
|
||||
BUILD_DIR="$WORK_DIR/build"
|
||||
INSTALL_DIR="$WORK_DIR/install"
|
||||
|
||||
mkdir -p "$INSTALL_DIR" "$SOURCES_DIR" "$WORK_DIR"
|
||||
|
||||
checksum() {
|
||||
local file="$1"
|
||||
local sum="$2"
|
||||
echo "$file: verifying checksum..."
|
||||
echo "$sum $file" | sha256sum -c
|
||||
}
|
||||
|
||||
get_file() {
|
||||
local url="$1"
|
||||
local file="$2"
|
||||
local sum="$3"
|
||||
if [[ -f "$file" ]]
|
||||
then
|
||||
echo "$file: found"
|
||||
else
|
||||
echo "$file: not found, downloading..."
|
||||
wget "$url" -O "$file"
|
||||
fi
|
||||
checksum "$file" "$sum"
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
|
||||
cd "$DEPS_DIR"
|
||||
. common
|
||||
|
||||
VERSION=6.1.1
|
||||
FILENAME=ffmpeg-$VERSION.tar.xz
|
||||
PROJECT_DIR=ffmpeg-$VERSION
|
||||
SHA256SUM=8684f4b00f94b85461884c3719382f1261f0d9eb3d59640a1f4ac0873616f968
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
if [[ -d "$PROJECT_DIR" ]]
|
||||
then
|
||||
echo "$PWD/$PROJECT_DIR" found
|
||||
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"
|
||||
cd "$BUILD_DIR/$PROJECT_DIR"
|
||||
|
||||
if [[ "$HOST" = win32 ]]
|
||||
then
|
||||
ARCH=x86
|
||||
elif [[ "$HOST" = win64 ]]
|
||||
then
|
||||
ARCH=x86_64
|
||||
else
|
||||
echo "Unsupported host: $HOST" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# -static-libgcc to avoid missing libgcc_s_dw2-1.dll
|
||||
# -static to avoid dynamic dependency to zlib
|
||||
export CFLAGS='-static-libgcc -static'
|
||||
export CXXFLAGS="$CFLAGS"
|
||||
export LDFLAGS='-static-libgcc -static'
|
||||
|
||||
if [[ -d "$HOST" ]]
|
||||
then
|
||||
echo "'$PWD/$HOST' already exists, not reconfigured"
|
||||
cd "$HOST"
|
||||
else
|
||||
mkdir "$HOST"
|
||||
cd "$HOST"
|
||||
|
||||
"$SOURCES_DIR/$PROJECT_DIR"/configure \
|
||||
--prefix="$INSTALL_DIR/$HOST" \
|
||||
--enable-cross-compile \
|
||||
--target-os=mingw32 \
|
||||
--arch="$ARCH" \
|
||||
--cross-prefix="${HOST_TRIPLET}-" \
|
||||
--cc="${HOST_TRIPLET}-gcc" \
|
||||
--extra-cflags="-O2 -fPIC" \
|
||||
--enable-shared \
|
||||
--disable-static \
|
||||
--disable-programs \
|
||||
--disable-doc \
|
||||
--disable-swscale \
|
||||
--disable-postproc \
|
||||
--disable-avfilter \
|
||||
--disable-avdevice \
|
||||
--disable-network \
|
||||
--disable-everything \
|
||||
--enable-swresample \
|
||||
--enable-decoder=h264 \
|
||||
--enable-decoder=hevc \
|
||||
--enable-decoder=av1 \
|
||||
--enable-decoder=pcm_s16le \
|
||||
--enable-decoder=opus \
|
||||
--enable-decoder=aac \
|
||||
--enable-decoder=flac \
|
||||
--enable-decoder=png \
|
||||
--enable-protocol=file \
|
||||
--enable-demuxer=image2 \
|
||||
--enable-parser=png \
|
||||
--enable-zlib \
|
||||
--enable-muxer=matroska \
|
||||
--enable-muxer=mp4 \
|
||||
--enable-muxer=opus \
|
||||
--enable-muxer=flac \
|
||||
--enable-muxer=wav \
|
||||
--disable-vulkan
|
||||
fi
|
||||
|
||||
make -j
|
||||
make install
|
||||
@@ -1,45 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
|
||||
cd "$DEPS_DIR"
|
||||
. common
|
||||
|
||||
VERSION=1.0.27
|
||||
FILENAME=libusb-$VERSION.tar.gz
|
||||
PROJECT_DIR=libusb-$VERSION
|
||||
SHA256SUM=e8f18a7a36ecbb11fb820bd71540350d8f61bcd9db0d2e8c18a6fb80b214a3de
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
if [[ -d "$PROJECT_DIR" ]]
|
||||
then
|
||||
echo "$PWD/$PROJECT_DIR" found
|
||||
else
|
||||
get_file "https://github.com/libusb/libusb/archive/refs/tags/v$VERSION.tar.gz" "$FILENAME" "$SHA256SUM"
|
||||
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
|
||||
fi
|
||||
|
||||
mkdir -p "$BUILD_DIR/$PROJECT_DIR"
|
||||
cd "$BUILD_DIR/$PROJECT_DIR"
|
||||
|
||||
export CFLAGS='-O2'
|
||||
export CXXFLAGS="$CFLAGS"
|
||||
|
||||
if [[ -d "$HOST" ]]
|
||||
then
|
||||
echo "'$PWD/$HOST' already exists, not reconfigured"
|
||||
cd "$HOST"
|
||||
else
|
||||
mkdir "$HOST"
|
||||
cd "$HOST"
|
||||
|
||||
"$SOURCES_DIR/$PROJECT_DIR"/bootstrap.sh
|
||||
"$SOURCES_DIR/$PROJECT_DIR"/configure \
|
||||
--prefix="$INSTALL_DIR/$HOST" \
|
||||
--host="$HOST_TRIPLET" \
|
||||
--enable-shared \
|
||||
--disable-static
|
||||
fi
|
||||
|
||||
make -j
|
||||
make install-strip
|
||||
@@ -1,27 +0,0 @@
|
||||
From 03c80197afb324da38c9b70254231e3fdcfa68fc Mon Sep 17 00:00:00 2001
|
||||
From: Romain Vimont <rom@rom1v.com>
|
||||
Date: Sun, 12 Nov 2023 17:58:50 +0100
|
||||
Subject: [PATCH] Fix FFmpeg 6.1 build
|
||||
|
||||
Build failed on tag n6.1 With --enable-decoder=av1 but without
|
||||
--enable-muxer=av1.
|
||||
---
|
||||
libavcodec/Makefile | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
|
||||
index 580a8d6b54..aff19b670c 100644
|
||||
--- a/libavcodec/Makefile
|
||||
+++ b/libavcodec/Makefile
|
||||
@@ -249,7 +249,7 @@ OBJS-$(CONFIG_ATRAC3PAL_DECODER) += atrac3plusdec.o atrac3plus.o \
|
||||
OBJS-$(CONFIG_ATRAC9_DECODER) += atrac9dec.o
|
||||
OBJS-$(CONFIG_AURA_DECODER) += cyuv.o
|
||||
OBJS-$(CONFIG_AURA2_DECODER) += aura.o
|
||||
-OBJS-$(CONFIG_AV1_DECODER) += av1dec.o
|
||||
+OBJS-$(CONFIG_AV1_DECODER) += av1dec.o av1_parse.o
|
||||
OBJS-$(CONFIG_AV1_CUVID_DECODER) += cuviddec.o
|
||||
OBJS-$(CONFIG_AV1_MEDIACODEC_DECODER) += mediacodecdec.o
|
||||
OBJS-$(CONFIG_AV1_MEDIACODEC_ENCODER) += mediacodecenc.o
|
||||
--
|
||||
2.42.0
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
|
||||
cd "$DEPS_DIR"
|
||||
. common
|
||||
|
||||
VERSION=2.28.5
|
||||
FILENAME=SDL-$VERSION.tar.gz
|
||||
PROJECT_DIR=SDL-release-$VERSION
|
||||
SHA256SUM=9f0556e4a24ef5b267010038ad9e9948b62f236d5bcc4b22179f95ef62d84023
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
if [[ -d "$PROJECT_DIR" ]]
|
||||
then
|
||||
echo "$PWD/$PROJECT_DIR" found
|
||||
else
|
||||
get_file "https://github.com/libsdl-org/SDL/archive/refs/tags/release-$VERSION.tar.gz" "$FILENAME" "$SHA256SUM"
|
||||
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
|
||||
fi
|
||||
|
||||
mkdir -p "$BUILD_DIR/$PROJECT_DIR"
|
||||
cd "$BUILD_DIR/$PROJECT_DIR"
|
||||
|
||||
export CFLAGS='-O2'
|
||||
export CXXFLAGS="$CFLAGS"
|
||||
|
||||
if [[ -d "$HOST" ]]
|
||||
then
|
||||
echo "'$PWD/$HOST' already exists, not reconfigured"
|
||||
cd "$HOST"
|
||||
else
|
||||
mkdir "$HOST"
|
||||
cd "$HOST"
|
||||
|
||||
"$SOURCES_DIR/$PROJECT_DIR"/configure \
|
||||
--prefix="$INSTALL_DIR/$HOST" \
|
||||
--host="$HOST_TRIPLET" \
|
||||
--enable-shared \
|
||||
--disable-static
|
||||
fi
|
||||
|
||||
make -j
|
||||
# There is no "make install-strip"
|
||||
make install
|
||||
# Strip manually
|
||||
${HOST_TRIPLET}-strip "$INSTALL_DIR/$HOST/bin/SDL2.dll"
|
||||
1
app/prebuilt-deps/.gitignore
vendored
Normal file
1
app/prebuilt-deps/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/data
|
||||
22
app/prebuilt-deps/common
Executable file
22
app/prebuilt-deps/common
Executable file
@@ -0,0 +1,22 @@
|
||||
PREBUILT_DATA_DIR=data
|
||||
|
||||
checksum() {
|
||||
local file="$1"
|
||||
local sum="$2"
|
||||
echo "$file: verifying checksum..."
|
||||
echo "$sum $file" | sha256sum -c
|
||||
}
|
||||
|
||||
get_file() {
|
||||
local url="$1"
|
||||
local file="$2"
|
||||
local sum="$3"
|
||||
if [[ -f "$file" ]]
|
||||
then
|
||||
echo "$file: found"
|
||||
else
|
||||
echo "$file: not found, downloading..."
|
||||
wget "$url" -O "$file"
|
||||
fi
|
||||
checksum "$file" "$sum"
|
||||
}
|
||||
32
app/prebuilt-deps/prepare-adb.sh
Executable file
32
app/prebuilt-deps/prepare-adb.sh
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
DIR=$(dirname ${BASH_SOURCE[0]})
|
||||
cd "$DIR"
|
||||
. common
|
||||
mkdir -p "$PREBUILT_DATA_DIR"
|
||||
cd "$PREBUILT_DATA_DIR"
|
||||
|
||||
DEP_DIR=platform-tools-34.0.5
|
||||
|
||||
FILENAME=platform-tools_r34.0.5-windows.zip
|
||||
SHA256SUM=3f8320152704377de150418a3c4c9d07d16d80a6c0d0d8f7289c22c499e33571
|
||||
|
||||
if [[ -d "$DEP_DIR" ]]
|
||||
then
|
||||
echo "$DEP_DIR" found
|
||||
exit 0
|
||||
fi
|
||||
|
||||
get_file "https://dl.google.com/android/repository/$FILENAME" \
|
||||
"$FILENAME" "$SHA256SUM"
|
||||
|
||||
mkdir "$DEP_DIR"
|
||||
cd "$DEP_DIR"
|
||||
|
||||
ZIP_PREFIX=platform-tools
|
||||
unzip "../$FILENAME" \
|
||||
"$ZIP_PREFIX"/AdbWinApi.dll \
|
||||
"$ZIP_PREFIX"/AdbWinUsbApi.dll \
|
||||
"$ZIP_PREFIX"/adb.exe
|
||||
mv "$ZIP_PREFIX"/* .
|
||||
rmdir "$ZIP_PREFIX"
|
||||
30
app/prebuilt-deps/prepare-ffmpeg.sh
Executable file
30
app/prebuilt-deps/prepare-ffmpeg.sh
Executable file
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
DIR=$(dirname ${BASH_SOURCE[0]})
|
||||
cd "$DIR"
|
||||
. common
|
||||
mkdir -p "$PREBUILT_DATA_DIR"
|
||||
cd "$PREBUILT_DATA_DIR"
|
||||
|
||||
VERSION=6.1-scrcpy-3
|
||||
DEP_DIR="ffmpeg-$VERSION"
|
||||
|
||||
FILENAME="$DEP_DIR".7z
|
||||
SHA256SUM=b646d18a3d543a4e4c46881568213499f22e4454a464e1552f03f2ac9cc3a05a
|
||||
|
||||
if [[ -d "$DEP_DIR" ]]
|
||||
then
|
||||
echo "$DEP_DIR" found
|
||||
exit 0
|
||||
fi
|
||||
|
||||
get_file "https://github.com/rom1v/scrcpy-deps/releases/download/$VERSION/$FILENAME" \
|
||||
"$FILENAME" "$SHA256SUM"
|
||||
|
||||
mkdir "$DEP_DIR"
|
||||
cd "$DEP_DIR"
|
||||
|
||||
ZIP_PREFIX=ffmpeg
|
||||
7z x "../$FILENAME"
|
||||
mv "$ZIP_PREFIX"/* .
|
||||
rmdir "$ZIP_PREFIX"
|
||||
39
app/prebuilt-deps/prepare-libusb.sh
Executable file
39
app/prebuilt-deps/prepare-libusb.sh
Executable file
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
DIR=$(dirname ${BASH_SOURCE[0]})
|
||||
cd "$DIR"
|
||||
. common
|
||||
mkdir -p "$PREBUILT_DATA_DIR"
|
||||
cd "$PREBUILT_DATA_DIR"
|
||||
|
||||
VERSION=1.0.26
|
||||
DEP_DIR="libusb-$VERSION"
|
||||
|
||||
FILENAME="libusb-$VERSION-binaries.7z"
|
||||
SHA256SUM=9c242696342dbde9cdc47239391f71833939bf9f7aa2bbb28cdaabe890465ec5
|
||||
|
||||
if [[ -d "$DEP_DIR" ]]
|
||||
then
|
||||
echo "$DEP_DIR" found
|
||||
exit 0
|
||||
fi
|
||||
|
||||
get_file "https://github.com/libusb/libusb/releases/download/v$VERSION/$FILENAME" \
|
||||
"$FILENAME" "$SHA256SUM"
|
||||
|
||||
mkdir "$DEP_DIR"
|
||||
cd "$DEP_DIR"
|
||||
|
||||
7z x "../$FILENAME" \
|
||||
"libusb-$VERSION-binaries/libusb-MinGW-Win32/" \
|
||||
"libusb-$VERSION-binaries/libusb-MinGW-Win32/" \
|
||||
"libusb-$VERSION-binaries/libusb-MinGW-x64/" \
|
||||
"libusb-$VERSION-binaries/libusb-MinGW-x64/"
|
||||
|
||||
mv "libusb-$VERSION-binaries/libusb-MinGW-Win32" .
|
||||
mv "libusb-$VERSION-binaries/libusb-MinGW-x64" .
|
||||
rm -rf "libusb-$VERSION-binaries"
|
||||
|
||||
# Rename the dll to get the same library name on all platforms
|
||||
mv libusb-MinGW-Win32/bin/msys-usb-1.0.dll libusb-MinGW-Win32/bin/libusb-1.0.dll
|
||||
mv libusb-MinGW-x64/bin/msys-usb-1.0.dll libusb-MinGW-x64/bin/libusb-1.0.dll
|
||||
34
app/prebuilt-deps/prepare-sdl.sh
Executable file
34
app/prebuilt-deps/prepare-sdl.sh
Executable file
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
DIR=$(dirname ${BASH_SOURCE[0]})
|
||||
cd "$DIR"
|
||||
. common
|
||||
mkdir -p "$PREBUILT_DATA_DIR"
|
||||
cd "$PREBUILT_DATA_DIR"
|
||||
|
||||
VERSION=2.28.5
|
||||
DEP_DIR="SDL2-$VERSION"
|
||||
|
||||
FILENAME="SDL2-devel-$VERSION-mingw.tar.gz"
|
||||
SHA256SUM=3c0c655c2ebf67cad48fead72761d1601740ded30808952c3274ba223d226c21
|
||||
|
||||
if [[ -d "$DEP_DIR" ]]
|
||||
then
|
||||
echo "$DEP_DIR" found
|
||||
exit 0
|
||||
fi
|
||||
|
||||
get_file "https://github.com/libsdl-org/SDL/releases/download/release-$VERSION/$FILENAME" \
|
||||
"$FILENAME" "$SHA256SUM"
|
||||
|
||||
mkdir "$DEP_DIR"
|
||||
cd "$DEP_DIR"
|
||||
|
||||
TAR_PREFIX="$DEP_DIR" # root directory inside the tar has the same name
|
||||
tar xf "../$FILENAME" --strip-components=1 \
|
||||
"$TAR_PREFIX"/i686-w64-mingw32/bin/SDL2.dll \
|
||||
"$TAR_PREFIX"/i686-w64-mingw32/include/ \
|
||||
"$TAR_PREFIX"/i686-w64-mingw32/lib/ \
|
||||
"$TAR_PREFIX"/x86_64-w64-mingw32/bin/SDL2.dll \
|
||||
"$TAR_PREFIX"/x86_64-w64-mingw32/include/ \
|
||||
"$TAR_PREFIX"/x86_64-w64-mingw32/lib/ \
|
||||
@@ -13,7 +13,7 @@ BEGIN
|
||||
VALUE "LegalCopyright", "Romain Vimont, Genymobile"
|
||||
VALUE "OriginalFilename", "scrcpy.exe"
|
||||
VALUE "ProductName", "scrcpy"
|
||||
VALUE "ProductVersion", "2.4"
|
||||
VALUE "ProductVersion", "2.3.1"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
57
app/scrcpy.1
57
app/scrcpy.1
@@ -163,6 +163,10 @@ Start in fullscreen.
|
||||
.B \-\-force\-adb\-forward
|
||||
Do not attempt to use "adb reverse" to connect to the device.
|
||||
|
||||
.TP
|
||||
.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
|
||||
.B \-h, \-\-help
|
||||
Print this help.
|
||||
@@ -175,17 +179,19 @@ Same as \fB\-\-keyboard=uhid\fR.
|
||||
.BI "\-\-keyboard " mode
|
||||
Select how to send keyboard inputs to the device.
|
||||
|
||||
Possible values are "disabled", "sdk", "uhid" and "aoa":
|
||||
Possible values are "disabled", "sdk" and "aoa":
|
||||
|
||||
- "disabled" does not send keyboard inputs to the device.
|
||||
- "sdk" uses the Android system API to deliver keyboard events to applications.
|
||||
- "uhid" simulates a physical HID keyboard using the Linux HID kernel module on the device.
|
||||
- "aoa" simulates a physical HID keyboard using the AOAv2 protocol. It may only work over USB.
|
||||
- "uhid" simulates a physical HID keyboard using the Linux HID kernel module on the device.
|
||||
|
||||
For "uhid" and "aoa", the keyboard layout must be configured (once and for all) on the device, via Settings -> System -> Languages and input -> Physical keyboard. This settings page can be started directly using the shortcut MOD+k (except in OTG mode), or by executing:
|
||||
For "aoa" and "uhid", the keyboard layout must be configured (once and for all) on the device, via Settings -> System -> Languages and input -> Physical keyboard. This settings page can be started directly using the shortcut MOD+k (except in OTG mode) or by executing:
|
||||
|
||||
adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS
|
||||
|
||||
If mirroring is enabled, the shortcot MOD+k opens it.
|
||||
|
||||
This option is only available when the HID keyboard is enabled (or a physical keyboard is connected).
|
||||
|
||||
Also see \fB\-\-mouse\fR.
|
||||
@@ -244,36 +250,19 @@ Limit the framerate of screen capture (officially supported since Android 10, bu
|
||||
.BI "\-\-mouse " mode
|
||||
Select how to send mouse inputs to the device.
|
||||
|
||||
Possible values are "disabled", "sdk", "uhid" and "aoa":
|
||||
Possible values are "disabled", "sdk" and "aoa":
|
||||
|
||||
- "disabled" does not send mouse inputs to the device.
|
||||
- "sdk" uses the Android system API to deliver mouse events to applications.
|
||||
- "uhid" simulates a physical HID mouse using the Linux HID kernel module on the device.
|
||||
- "aoa" simulates a physical mouse using the AOAv2 protocol. It may only work over USB.
|
||||
- "uhid" simulates a physical HID mouse using the Linux HID kernel module on the device.
|
||||
|
||||
In "uhid" and "aoa" modes, the computer mouse is captured to control the device directly (relative mouse mode).
|
||||
In "aoa" and "uhid" modes, the computer mouse is captured to control the device directly (relative mouse mode).
|
||||
|
||||
LAlt, LSuper or RSuper toggle the capture mode, to give control of the mouse back to the computer.
|
||||
|
||||
Also see \fB\-\-keyboard\fR.
|
||||
|
||||
.TP
|
||||
.BI "\-\-mouse\-bind " xxxx
|
||||
Configure bindings of secondary clicks.
|
||||
|
||||
The argument must be exactly 4 characters, one for each secondary click (in order: right click, middle click, 4th click, 5th click).
|
||||
|
||||
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 '++++' for AOA and UHID.
|
||||
|
||||
|
||||
.TP
|
||||
.B \-n, \-\-no\-control
|
||||
@@ -317,10 +306,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.
|
||||
@@ -333,10 +318,6 @@ Disable video forwarding.
|
||||
.B \-\-no\-video\-playback
|
||||
Disable video playback on the computer.
|
||||
|
||||
.TP
|
||||
.B \-\-no\-window
|
||||
Disable scrcpy window. Implies --no-video-playback and --no-control.
|
||||
|
||||
.TP
|
||||
.BI "\-\-orientation " value
|
||||
Same as --display-orientation=value --record-orientation=value.
|
||||
@@ -441,9 +422,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).
|
||||
|
||||
@@ -598,14 +579,6 @@ Flip display horizontally
|
||||
.B MOD+Shift+Up, MOD+Shift+Down
|
||||
Flip display vertically
|
||||
|
||||
.TP
|
||||
.B MOD+z
|
||||
Pause or re-pause display
|
||||
|
||||
.TP
|
||||
.B MOD+Shift+z
|
||||
Unpause display
|
||||
|
||||
.TP
|
||||
.B MOD+g
|
||||
Resize window to 1:1 (pixel\-perfect)
|
||||
@@ -743,7 +716,7 @@ Report bugs to <https://github.com/Genymobile/scrcpy/issues>.
|
||||
.SH COPYRIGHT
|
||||
Copyright \(co 2018 Genymobile <https://www.genymobile.com>
|
||||
|
||||
Copyright \(co 2018\-2024 Romain Vimont <rom@rom1v.com>
|
||||
Copyright \(co 2018\-2023 Romain Vimont <rom@rom1v.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0.
|
||||
|
||||
|
||||
@@ -194,11 +194,7 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
|
||||
// Still insufficient, drop old samples to make space
|
||||
skipped_samples = sc_audiobuf_read(&ap->buf, NULL, remaining);
|
||||
assert(skipped_samples == remaining);
|
||||
}
|
||||
|
||||
SDL_UnlockAudioDevice(ap->device);
|
||||
|
||||
if (written < samples) {
|
||||
// Now there is enough space
|
||||
uint32_t w = sc_audiobuf_write(&ap->buf,
|
||||
swr_buf + TO_BYTES(written),
|
||||
@@ -206,6 +202,8 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
|
||||
assert(w == remaining);
|
||||
(void) w;
|
||||
}
|
||||
|
||||
SDL_UnlockAudioDevice(ap->device);
|
||||
}
|
||||
|
||||
uint32_t underflow = 0;
|
||||
|
||||
396
app/src/cli.c
396
app/src/cli.c
@@ -97,9 +97,6 @@ enum {
|
||||
OPT_MOUSE,
|
||||
OPT_HID_KEYBOARD_DEPRECATED,
|
||||
OPT_HID_MOUSE_DEPRECATED,
|
||||
OPT_NO_WINDOW,
|
||||
OPT_MOUSE_BIND,
|
||||
OPT_NO_MOUSE_HOVER,
|
||||
};
|
||||
|
||||
struct sc_option {
|
||||
@@ -354,9 +351,11 @@ static const struct sc_option options[] = {
|
||||
"device.",
|
||||
},
|
||||
{
|
||||
// deprecated
|
||||
.longopt_id = OPT_FORWARD_ALL_CLICKS,
|
||||
.longopt = "forward-all-clicks",
|
||||
.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',
|
||||
@@ -372,23 +371,23 @@ static const struct sc_option options[] = {
|
||||
.longopt = "keyboard",
|
||||
.argdesc = "mode",
|
||||
.text = "Select how to send keyboard inputs to the device.\n"
|
||||
"Possible values are \"disabled\", \"sdk\", \"uhid\" and "
|
||||
"\"aoa\".\n"
|
||||
"Possible values are \"disabled\", \"sdk\", \"aoa\" and "
|
||||
"\"uhid\".\n"
|
||||
"\"disabled\" does not send keyboard inputs to the device.\n"
|
||||
"\"sdk\" uses the Android system API to deliver keyboard "
|
||||
"\"sdk\" uses the Android system API to deliver keyboard\n"
|
||||
"events to applications.\n"
|
||||
"\"uhid\" simulates a physical HID keyboard using the Linux "
|
||||
"UHID kernel module on the device.\n"
|
||||
"\"aoa\" simulates a physical keyboard using the AOAv2 "
|
||||
"\"aoa\" simulates a physical HID keyboard using the AOAv2\n"
|
||||
"protocol. It may only work over USB.\n"
|
||||
"For \"uhid\" and \"aoa\", the keyboard layout must be "
|
||||
"\"uhid\" simulates a physical HID keyboard using the Linux "
|
||||
"UHID kernel module on the device."
|
||||
"For \"aoa\" and \"uhid\", the keyboard layout must be "
|
||||
"configured (once and for all) on the device, via Settings -> "
|
||||
"System -> Languages and input -> Physical keyboard. This "
|
||||
"settings page can be started directly using the shortcut "
|
||||
"MOD+k (except in OTG mode) or by executing: `adb shell am "
|
||||
"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"
|
||||
"enabled (or a physical keyboard is connected).\n"
|
||||
"Also see --mouse.",
|
||||
},
|
||||
{
|
||||
@@ -475,38 +474,20 @@ static const struct sc_option options[] = {
|
||||
.longopt = "mouse",
|
||||
.argdesc = "mode",
|
||||
.text = "Select how to send mouse inputs to the device.\n"
|
||||
"Possible values are \"disabled\", \"sdk\", \"uhid\" and "
|
||||
"\"aoa\".\n"
|
||||
"Possible values are \"disabled\", \"sdk\" and \"aoa\".\n"
|
||||
"\"disabled\" does not send mouse inputs to the device.\n"
|
||||
"\"sdk\" uses the Android system API to deliver mouse events"
|
||||
"to applications.\n"
|
||||
"\"uhid\" simulates a physical HID mouse using the Linux UHID "
|
||||
"kernel module on the device.\n"
|
||||
"\"aoa\" simulates a physical mouse using the AOAv2 protocol. "
|
||||
"\"sdk\" uses the Android system API to deliver mouse\n"
|
||||
"events to applications.\n"
|
||||
"\"aoa\" simulates a physical mouse using the AOAv2 protocol\n"
|
||||
"It may only work over USB.\n"
|
||||
"In \"uhid\" and \"aoa\" modes, the computer mouse is captured "
|
||||
"\"uhid\" simulates a physical HID mouse using the Linux UHID "
|
||||
"kernel module on the device."
|
||||
"In \"aoa\" and \"uhid\" modes, the computer mouse is captured "
|
||||
"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.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_MOUSE_BIND,
|
||||
.longopt = "mouse-bind",
|
||||
.argdesc = "xxxx",
|
||||
.text = "Configure bindings of secondary clicks.\n"
|
||||
"The argument must be exactly 4 characters, one for each "
|
||||
"secondary click (in order: right click, middle click, 4th "
|
||||
"click, 5th click).\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 '++++' for AOA and UHID.",
|
||||
},
|
||||
{
|
||||
.shortopt = 'n',
|
||||
.longopt = "no-control",
|
||||
@@ -569,12 +550,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",
|
||||
@@ -590,12 +565,6 @@ static const struct sc_option options[] = {
|
||||
.longopt = "no-video-playback",
|
||||
.text = "Disable video playback on the computer.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_NO_WINDOW,
|
||||
.longopt = "no-window",
|
||||
.text = "Disable scrcpy window. Implies --no-video-playback and "
|
||||
"--no-control.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_ORIENTATION,
|
||||
.longopt = "orientation",
|
||||
@@ -613,7 +582,7 @@ static const struct sc_option options[] = {
|
||||
"mirroring is disabled.\n"
|
||||
"LAlt, LSuper or RSuper toggle the mouse capture mode, to give "
|
||||
"control of the mouse back to the computer.\n"
|
||||
"Keyboard and mouse may be disabled separately using"
|
||||
"Keyboard and mouse may be disabled separately using\n"
|
||||
"--keyboard=disabled and --mouse=disabled.\n"
|
||||
"It may only work over USB.\n"
|
||||
"See --keyboard and --mouse.",
|
||||
@@ -739,10 +708,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).",
|
||||
},
|
||||
{
|
||||
@@ -930,14 +899,6 @@ static const struct sc_shortcut shortcuts[] = {
|
||||
.shortcuts = { "MOD+Shift+Up", "MOD+Shift+Down" },
|
||||
.text = "Flip display vertically",
|
||||
},
|
||||
{
|
||||
.shortcuts = { "MOD+z" },
|
||||
.text = "Pause or re-pause display",
|
||||
},
|
||||
{
|
||||
.shortcuts = { "MOD+Shift+z" },
|
||||
.text = "Unpause display",
|
||||
},
|
||||
{
|
||||
.shortcuts = { "MOD+g" },
|
||||
.text = "Resize window to 1:1 (pixel-perfect)",
|
||||
@@ -1710,62 +1671,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;
|
||||
@@ -1774,7 +1755,7 @@ parse_shortcut_mods(const char *s, uint8_t *shortcut_mods) {
|
||||
s = comma + 1;
|
||||
}
|
||||
|
||||
*shortcut_mods = mods;
|
||||
mods->count = count;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1782,7 +1763,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
|
||||
@@ -1980,11 +1961,6 @@ parse_keyboard(const char *optarg, enum sc_keyboard_input_mode *mode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!strcmp(optarg, "uhid")) {
|
||||
*mode = SC_KEYBOARD_INPUT_MODE_UHID;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!strcmp(optarg, "aoa")) {
|
||||
#ifdef HAVE_USB
|
||||
*mode = SC_KEYBOARD_INPUT_MODE_AOA;
|
||||
@@ -1995,7 +1971,12 @@ parse_keyboard(const char *optarg, enum sc_keyboard_input_mode *mode) {
|
||||
#endif
|
||||
}
|
||||
|
||||
LOGE("Unsupported keyboard: %s (expected disabled, sdk, uhid and aoa)",
|
||||
if (!strcmp(optarg, "uhid")) {
|
||||
*mode = SC_KEYBOARD_INPUT_MODE_UHID;
|
||||
return true;
|
||||
}
|
||||
|
||||
LOGE("Unsupported keyboard: %s (expected disabled, sdk, aoa or uhid)",
|
||||
optarg);
|
||||
return false;
|
||||
}
|
||||
@@ -2012,11 +1993,6 @@ parse_mouse(const char *optarg, enum sc_mouse_input_mode *mode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!strcmp(optarg, "uhid")) {
|
||||
*mode = SC_MOUSE_INPUT_MODE_UHID;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!strcmp(optarg, "aoa")) {
|
||||
#ifdef HAVE_USB
|
||||
*mode = SC_MOUSE_INPUT_MODE_AOA;
|
||||
@@ -2027,7 +2003,12 @@ parse_mouse(const char *optarg, enum sc_mouse_input_mode *mode) {
|
||||
#endif
|
||||
}
|
||||
|
||||
LOGE("Unsupported mouse: %s (expected disabled, sdk, uhid or aoa)", optarg);
|
||||
if (!strcmp(optarg, "uhid")) {
|
||||
*mode = SC_MOUSE_INPUT_MODE_UHID;
|
||||
return true;
|
||||
}
|
||||
|
||||
LOGE("Unsupported mouse: %s (expected disabled, sdk, aoa or uhid)", optarg);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2061,63 +2042,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_bindings(const char *s, struct sc_mouse_bindings *mb) {
|
||||
if (strlen(s) != 4) {
|
||||
LOGE("Invalid mouse bindings: '%s' (expected exactly 4 characters from "
|
||||
"{'+', '-', 'b', 'h', 's', 'n'})", s);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!parse_mouse_binding(s[0], &mb->right_click)) {
|
||||
return false;
|
||||
}
|
||||
if (!parse_mouse_binding(s[1], &mb->middle_click)) {
|
||||
return false;
|
||||
}
|
||||
if (!parse_mouse_binding(s[2], &mb->click4)) {
|
||||
return false;
|
||||
}
|
||||
if (!parse_mouse_binding(s[3], &mb->click5)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
const char *optstring, const struct option *longopts) {
|
||||
@@ -2200,14 +2129,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
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.");
|
||||
@@ -2405,14 +2326,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) {
|
||||
.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;
|
||||
@@ -2563,9 +2477,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
case OPT_CAMERA_HIGH_SPEED:
|
||||
opts->camera_high_speed = true;
|
||||
break;
|
||||
case OPT_NO_WINDOW:
|
||||
opts->window = false;
|
||||
break;
|
||||
default:
|
||||
// getopt prints the error message on stderr
|
||||
return false;
|
||||
@@ -2603,12 +2514,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
v4l2 = !!opts->v4l2_device;
|
||||
#endif
|
||||
|
||||
if (!opts->window) {
|
||||
// Without window, there cannot be any video playback or control
|
||||
opts->video_playback = false;
|
||||
opts->control = false;
|
||||
}
|
||||
|
||||
if (!opts->video) {
|
||||
opts->video_playback = false;
|
||||
// Do not power on the device on start if video capture is disabled
|
||||
@@ -2630,8 +2535,8 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
opts->audio = false;
|
||||
}
|
||||
|
||||
if (!opts->video && !opts->audio && !opts->control && !otg) {
|
||||
LOGE("No video, no audio, no control, no OTG: nothing to do");
|
||||
if (!opts->video && !opts->audio && !otg) {
|
||||
LOGE("No video, no audio, no OTG: nothing to do");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2642,9 +2547,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
|
||||
if (opts->audio_playback && opts->audio_buffer == -1) {
|
||||
if (opts->audio_codec == SC_CODEC_FLAC) {
|
||||
// Use 50 ms audio buffer by default, but use a higher value for
|
||||
// FLAC, which is not low latency (the default encoder produces
|
||||
// blocks of 4096 samples, which represent ~85.333ms).
|
||||
// Use 50 ms audio buffer by default, but use a higher value for FLAC,
|
||||
// which is not low latency (the default encoder produces blocks of
|
||||
// 4096 samples, which represent ~85.333ms).
|
||||
LOGI("FLAC audio: audio buffer increased to 120 ms (use "
|
||||
"--audio-buffer to set a custom value)");
|
||||
opts->audio_buffer = SC_TICK_FROM_MS(120);
|
||||
@@ -2655,11 +2560,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
|
||||
#ifdef HAVE_V4L2
|
||||
if (v4l2) {
|
||||
if (!opts->video) {
|
||||
LOGE("V4L2 sink requires video capture, but --no-video was set.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opts->lock_video_orientation ==
|
||||
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
|
||||
LOGI("Video orientation is locked for v4l2 sink. "
|
||||
@@ -2679,57 +2579,16 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
}
|
||||
#endif
|
||||
|
||||
if (opts->control) {
|
||||
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;
|
||||
}
|
||||
if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_AUTO) {
|
||||
if (otg) {
|
||||
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_AOA;
|
||||
} else if (!opts->video_playback) {
|
||||
LOGI("No video mirroring, mouse mode switched to UHID");
|
||||
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_UHID;
|
||||
} else {
|
||||
opts->mouse_input_mode = 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->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AUTO) {
|
||||
opts->keyboard_input_mode = otg ? SC_KEYBOARD_INPUT_MODE_AOA
|
||||
: SC_KEYBOARD_INPUT_MODE_SDK;
|
||||
}
|
||||
|
||||
// If mouse bindings are not explictly set, configure default bindings
|
||||
if (opts->mouse_bindings.right_click == SC_MOUSE_BINDING_AUTO) {
|
||||
assert(opts->mouse_bindings.middle_click == SC_MOUSE_BINDING_AUTO);
|
||||
assert(opts->mouse_bindings.click4 == SC_MOUSE_BINDING_AUTO);
|
||||
assert(opts->mouse_bindings.click5 == SC_MOUSE_BINDING_AUTO);
|
||||
|
||||
// By default, forward all clicks only for UHID and AOA
|
||||
if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK) {
|
||||
opts->mouse_bindings = (struct sc_mouse_bindings) {
|
||||
.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,
|
||||
};
|
||||
} else {
|
||||
opts->mouse_bindings = (struct sc_mouse_bindings) {
|
||||
.right_click = SC_MOUSE_BINDING_CLICK,
|
||||
.middle_click = SC_MOUSE_BINDING_CLICK,
|
||||
.click4 = SC_MOUSE_BINDING_CLICK,
|
||||
.click5 = SC_MOUSE_BINDING_CLICK,
|
||||
};
|
||||
}
|
||||
if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_AUTO) {
|
||||
opts->mouse_input_mode = otg ? SC_MOUSE_INPUT_MODE_AOA
|
||||
: SC_MOUSE_INPUT_MODE_SDK;
|
||||
}
|
||||
|
||||
if (otg) {
|
||||
if (!opts->control) {
|
||||
LOGE("--no-control is not allowed in OTG mode");
|
||||
return false;
|
||||
}
|
||||
|
||||
enum sc_keyboard_input_mode kmode = opts->keyboard_input_mode;
|
||||
if (kmode != SC_KEYBOARD_INPUT_MODE_AOA
|
||||
&& kmode != SC_KEYBOARD_INPUT_MODE_DISABLED) {
|
||||
@@ -2743,35 +2602,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
LOGE("In OTG mode, --mouse only supports aoa or disabled.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (kmode == SC_KEYBOARD_INPUT_MODE_DISABLED
|
||||
&& mmode == SC_MOUSE_INPUT_MODE_DISABLED) {
|
||||
LOGE("Could not disable both keyboard and mouse in OTG mode.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (opts->keyboard_input_mode != SC_KEYBOARD_INPUT_MODE_SDK) {
|
||||
if (opts->key_inject_mode == SC_KEY_INJECT_MODE_TEXT) {
|
||||
LOGE("--prefer-text is specific to --keyboard=sdk");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opts->key_inject_mode == SC_KEY_INJECT_MODE_RAW) {
|
||||
LOGE("--raw-key-events is specific to --keyboard=sdk");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!opts->forward_key_repeat) {
|
||||
LOGE("--no-key-repeat is specific to --keyboard=sdk");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -2838,11 +2668,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
}
|
||||
|
||||
if (opts->record_filename) {
|
||||
if (!opts->video && !opts->audio) {
|
||||
LOGE("Video and audio disabled, nothing to record");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!opts->record_format) {
|
||||
opts->record_format = guess_record_format(opts->record_filename);
|
||||
if (!opts->record_format) {
|
||||
@@ -2949,11 +2774,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
}
|
||||
# endif
|
||||
|
||||
if (opts->start_fps_counter && !opts->video_playback) {
|
||||
LOGW("--print-fps has no effect without video playback");
|
||||
opts->start_fps_counter = false;
|
||||
}
|
||||
|
||||
if (otg) {
|
||||
// OTG mode is compatible with only very few options.
|
||||
// Only report obvious errors.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -157,6 +157,9 @@ 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_SET_CAMERA_TORCH:
|
||||
buf[1] = msg->set_camera_torch.enabled ? 1 : 0;
|
||||
return 2;
|
||||
case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||
case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
|
||||
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
||||
@@ -274,6 +277,10 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
|
||||
case SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
||||
LOG_CMSG("open hard keyboard settings");
|
||||
break;
|
||||
case SC_CONTROL_MSG_TYPE_SET_CAMERA_TORCH:
|
||||
LOG_CMSG("%s camera torch",
|
||||
msg->set_camera_torch.enabled ? "enable" : "disable");
|
||||
break;
|
||||
default:
|
||||
LOG_CMSG("unknown type: %u", (unsigned) msg->type);
|
||||
break;
|
||||
|
||||
@@ -41,6 +41,7 @@ enum sc_control_msg_type {
|
||||
SC_CONTROL_MSG_TYPE_UHID_CREATE,
|
||||
SC_CONTROL_MSG_TYPE_UHID_INPUT,
|
||||
SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS,
|
||||
SC_CONTROL_MSG_TYPE_SET_CAMERA_TORCH,
|
||||
};
|
||||
|
||||
enum sc_screen_power_mode {
|
||||
@@ -106,6 +107,9 @@ struct sc_control_msg {
|
||||
uint16_t size;
|
||||
uint8_t data[SC_HID_MAX_SIZE];
|
||||
} uhid_input;
|
||||
struct {
|
||||
bool enabled;
|
||||
} set_camera_torch;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -6,19 +6,8 @@
|
||||
|
||||
#define SC_CONTROL_MSG_QUEUE_MAX 64
|
||||
|
||||
static void
|
||||
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_error(controller, controller->cbs_userdata);
|
||||
}
|
||||
|
||||
bool
|
||||
sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
|
||||
const struct sc_controller_callbacks *cbs,
|
||||
void *cbs_userdata) {
|
||||
sc_controller_init(struct sc_controller *controller, sc_socket control_socket) {
|
||||
sc_vecdeque_init(&controller->queue);
|
||||
|
||||
bool ok = sc_vecdeque_reserve(&controller->queue, SC_CONTROL_MSG_QUEUE_MAX);
|
||||
@@ -26,12 +15,7 @@ sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
|
||||
return false;
|
||||
}
|
||||
|
||||
static const struct sc_receiver_callbacks receiver_cbs = {
|
||||
.on_error = sc_controller_receiver_on_error,
|
||||
};
|
||||
|
||||
ok = sc_receiver_init(&controller->receiver, control_socket, &receiver_cbs,
|
||||
controller);
|
||||
ok = sc_receiver_init(&controller->receiver, control_socket);
|
||||
if (!ok) {
|
||||
sc_vecdeque_destroy(&controller->queue);
|
||||
return false;
|
||||
@@ -55,10 +39,6 @@ sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
|
||||
controller->control_socket = control_socket;
|
||||
controller->stopped = false;
|
||||
|
||||
assert(cbs && cbs->on_error);
|
||||
controller->cbs = cbs;
|
||||
controller->cbs_userdata = cbs_userdata;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -145,16 +125,10 @@ run_controller(void *data) {
|
||||
sc_control_msg_destroy(&msg);
|
||||
if (!ok) {
|
||||
LOGD("Could not write msg to socket");
|
||||
goto error;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
controller->cbs->on_error(controller, controller->cbs_userdata);
|
||||
|
||||
return 1; // ignored
|
||||
}
|
||||
|
||||
bool
|
||||
|
||||
@@ -22,19 +22,10 @@ struct sc_controller {
|
||||
bool stopped;
|
||||
struct sc_control_msg_queue queue;
|
||||
struct sc_receiver receiver;
|
||||
|
||||
const struct sc_controller_callbacks *cbs;
|
||||
void *cbs_userdata;
|
||||
};
|
||||
|
||||
struct sc_controller_callbacks {
|
||||
void (*on_error)(struct sc_controller *controller, void *userdata);
|
||||
};
|
||||
|
||||
bool
|
||||
sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
|
||||
const struct sc_controller_callbacks *cbs,
|
||||
void *cbs_userdata);
|
||||
sc_controller_init(struct sc_controller *controller, sc_socket control_socket);
|
||||
|
||||
void
|
||||
sc_controller_configure(struct sc_controller *controller,
|
||||
|
||||
@@ -1,34 +1,11 @@
|
||||
#include "display.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <libavutil/pixfmt.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
static bool
|
||||
sc_display_init_novideo_icon(struct sc_display *display,
|
||||
SDL_Surface *icon_novideo) {
|
||||
assert(icon_novideo);
|
||||
|
||||
if (SDL_RenderSetLogicalSize(display->renderer,
|
||||
icon_novideo->w, icon_novideo->h)) {
|
||||
LOGW("Could not set renderer logical size: %s", SDL_GetError());
|
||||
// don't fail
|
||||
}
|
||||
|
||||
display->texture = SDL_CreateTextureFromSurface(display->renderer,
|
||||
icon_novideo);
|
||||
if (!display->texture) {
|
||||
LOGE("Could not create texture: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_display_init(struct sc_display *display, SDL_Window *window,
|
||||
SDL_Surface *icon_novideo, bool mipmaps) {
|
||||
sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps) {
|
||||
display->renderer =
|
||||
SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
||||
if (!display->renderer) {
|
||||
@@ -82,25 +59,11 @@ sc_display_init(struct sc_display *display, SDL_Window *window,
|
||||
LOGI("Trilinear filtering disabled");
|
||||
}
|
||||
} else if (mipmaps) {
|
||||
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
|
||||
LOGD("Trilinear filtering disabled (not an OpenGL renderer");
|
||||
}
|
||||
|
||||
display->texture = NULL;
|
||||
display->pending.flags = 0;
|
||||
display->pending.frame = NULL;
|
||||
display->has_frame = false;
|
||||
|
||||
if (icon_novideo) {
|
||||
// Without video, set a static scrcpy icon as window content
|
||||
bool ok = sc_display_init_novideo_icon(display, icon_novideo);
|
||||
if (!ok) {
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
SDL_GL_DeleteContext(display->gl_context);
|
||||
#endif
|
||||
SDL_DestroyRenderer(display->renderer);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -232,25 +195,9 @@ sc_display_set_texture_size(struct sc_display *display, struct sc_size size) {
|
||||
return SC_DISPLAY_RESULT_OK;
|
||||
}
|
||||
|
||||
static SDL_YUV_CONVERSION_MODE
|
||||
sc_display_to_sdl_color_range(enum AVColorRange color_range) {
|
||||
return color_range == AVCOL_RANGE_JPEG ? SDL_YUV_CONVERSION_JPEG
|
||||
: SDL_YUV_CONVERSION_AUTOMATIC;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_display_update_texture_internal(struct sc_display *display,
|
||||
const AVFrame *frame) {
|
||||
if (!display->has_frame) {
|
||||
// First frame
|
||||
display->has_frame = true;
|
||||
|
||||
// Configure YUV color range conversion
|
||||
SDL_YUV_CONVERSION_MODE sdl_color_range =
|
||||
sc_display_to_sdl_color_range(frame->color_range);
|
||||
SDL_SetYUVConversionMode(sdl_color_range);
|
||||
}
|
||||
|
||||
int ret = SDL_UpdateYUVTexture(display->texture, NULL,
|
||||
frame->data[0], frame->linesize[0],
|
||||
frame->data[1], frame->linesize[1],
|
||||
|
||||
@@ -33,8 +33,6 @@ struct sc_display {
|
||||
struct sc_size size;
|
||||
AVFrame *frame;
|
||||
} pending;
|
||||
|
||||
bool has_frame;
|
||||
};
|
||||
|
||||
enum sc_display_result {
|
||||
@@ -44,8 +42,7 @@ enum sc_display_result {
|
||||
};
|
||||
|
||||
bool
|
||||
sc_display_init(struct sc_display *display, SDL_Window *window,
|
||||
SDL_Surface *icon_novideo, bool mipmaps);
|
||||
sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps);
|
||||
|
||||
void
|
||||
sc_display_destroy(struct sc_display *display);
|
||||
|
||||
@@ -7,4 +7,3 @@
|
||||
#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)
|
||||
|
||||
@@ -12,4 +12,7 @@ struct sc_hid_event {
|
||||
uint8_t size;
|
||||
};
|
||||
|
||||
char *
|
||||
sc_hid_event_to_string(const struct sc_hid_event *event, size_t max_data_bytes);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -78,19 +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);
|
||||
int stream = av_find_best_stream(ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
|
||||
if (stream < 0 ) {
|
||||
LOGE("Could not find best image stream");
|
||||
goto close_input;
|
||||
@@ -98,6 +86,12 @@ decode_image(const char *path) {
|
||||
|
||||
AVCodecParameters *params = ctx->streams[stream]->codecpar;
|
||||
|
||||
const AVCodec *codec = avcodec_find_decoder(params->codec_id);
|
||||
if (!codec) {
|
||||
LOGE("Could not find image decoder");
|
||||
goto close_input;
|
||||
}
|
||||
|
||||
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
|
||||
if (!codec_ctx) {
|
||||
LOG_OOM();
|
||||
|
||||
@@ -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.
|
||||
@@ -438,21 +437,15 @@ sc_mouse_button_from_sdl(uint8_t button) {
|
||||
|
||||
static inline uint8_t
|
||||
sc_mouse_buttons_state_from_sdl(uint32_t buttons_state,
|
||||
const struct sc_mouse_bindings *mb) {
|
||||
bool forward_all_clicks) {
|
||||
assert(buttons_state < 0x100); // fits in uint8_t
|
||||
|
||||
uint8_t mask = SC_MOUSE_BUTTON_LEFT;
|
||||
if (!mb || mb->right_click == SC_MOUSE_BINDING_CLICK) {
|
||||
mask |= SC_MOUSE_BUTTON_RIGHT;
|
||||
}
|
||||
if (!mb || mb->middle_click == SC_MOUSE_BINDING_CLICK) {
|
||||
mask |= SC_MOUSE_BUTTON_MIDDLE;
|
||||
}
|
||||
if (!mb || mb->click4 == SC_MOUSE_BINDING_CLICK) {
|
||||
mask |= SC_MOUSE_BUTTON_X1;
|
||||
}
|
||||
if (!mb || mb->click5 == SC_MOUSE_BINDING_CLICK) {
|
||||
mask |= SC_MOUSE_BUTTON_X2;
|
||||
if (forward_all_clicks) {
|
||||
mask |= SC_MOUSE_BUTTON_RIGHT
|
||||
| SC_MOUSE_BUTTON_MIDDLE
|
||||
| SC_MOUSE_BUTTON_X1
|
||||
| SC_MOUSE_BUTTON_X2;
|
||||
}
|
||||
|
||||
return buttons_state & mask;
|
||||
|
||||
@@ -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,26 +38,15 @@ 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);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
mouse_bindings_has_secondary_click(const struct sc_mouse_bindings *mb) {
|
||||
return mb->right_click == SC_MOUSE_BINDING_CLICK
|
||||
|| mb->middle_click == SC_MOUSE_BINDING_CLICK
|
||||
|| mb->click4 == SC_MOUSE_BINDING_CLICK
|
||||
|| mb->click5 == SC_MOUSE_BINDING_CLICK;
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -75,13 +64,19 @@ sc_input_manager_init(struct sc_input_manager *im,
|
||||
im->kp = params->kp;
|
||||
im->mp = params->mp;
|
||||
|
||||
im->mouse_bindings = params->mouse_bindings;
|
||||
im->has_secondary_click =
|
||||
mouse_bindings_has_secondary_click(&im->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;
|
||||
@@ -335,6 +330,19 @@ open_hard_keyboard_settings(struct sc_input_manager *im) {
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
set_camera_torch(struct sc_input_manager *im, bool enabled) {
|
||||
assert(im->controller);
|
||||
|
||||
struct sc_control_msg msg;
|
||||
msg.type = SC_CONTROL_MSG_TYPE_SET_CAMERA_TORCH;
|
||||
msg.set_camera_torch.enabled = enabled;
|
||||
|
||||
if (!sc_controller_push_msg(im->controller, &msg)) {
|
||||
LOGW("Could not request setting camera torch");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
apply_orientation_transform(struct sc_input_manager *im,
|
||||
enum sc_orientation transform) {
|
||||
@@ -376,8 +384,8 @@ simulate_virtual_finger(struct sc_input_manager *im,
|
||||
msg.inject_touch_event.position.screen_size = im->screen->frame_size;
|
||||
msg.inject_touch_event.position.point = point;
|
||||
msg.inject_touch_event.pointer_id =
|
||||
im->has_secondary_click ? POINTER_ID_VIRTUAL_MOUSE
|
||||
: POINTER_ID_VIRTUAL_FINGER;
|
||||
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;
|
||||
@@ -407,8 +415,6 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
const SDL_KeyboardEvent *event) {
|
||||
// controller is NULL if --no-control is requested
|
||||
bool control = im->controller;
|
||||
bool paused = im->screen->paused;
|
||||
bool video = im->screen->video;
|
||||
|
||||
SDL_Keycode keycode = event->keysym.sym;
|
||||
uint16_t mod = event->keysym.mod;
|
||||
@@ -417,12 +423,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) {
|
||||
@@ -434,72 +435,68 @@ 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:
|
||||
if (im->kp && !shift && !repeat && !paused) {
|
||||
if (im->kp && !shift && !repeat) {
|
||||
action_home(im, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_b: // fall-through
|
||||
case SDLK_BACKSPACE:
|
||||
if (im->kp && !shift && !repeat && !paused) {
|
||||
if (im->kp && !shift && !repeat) {
|
||||
action_back(im, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_s:
|
||||
if (im->kp && !shift && !repeat && !paused) {
|
||||
if (im->kp && !shift && !repeat) {
|
||||
action_app_switch(im, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_m:
|
||||
if (im->kp && !shift && !repeat && !paused) {
|
||||
if (im->kp && !shift && !repeat) {
|
||||
action_menu(im, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_p:
|
||||
if (im->kp && !shift && !repeat && !paused) {
|
||||
if (im->kp && !shift && !repeat) {
|
||||
action_power(im, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_o:
|
||||
if (control && !repeat && down && !paused) {
|
||||
if (control && !repeat && down) {
|
||||
enum sc_screen_power_mode mode = shift
|
||||
? SC_SCREEN_POWER_MODE_NORMAL
|
||||
: SC_SCREEN_POWER_MODE_OFF;
|
||||
set_screen_power_mode(im, mode);
|
||||
}
|
||||
return;
|
||||
case SDLK_z:
|
||||
if (video && down && !repeat) {
|
||||
sc_screen_set_paused(im->screen, !shift);
|
||||
}
|
||||
return;
|
||||
case SDLK_DOWN:
|
||||
if (shift) {
|
||||
if (video && !repeat && down) {
|
||||
if (!repeat & down) {
|
||||
apply_orientation_transform(im,
|
||||
SC_ORIENTATION_FLIP_180);
|
||||
}
|
||||
} else if (im->kp && !paused) {
|
||||
} else if (im->kp) {
|
||||
// forward repeated events
|
||||
action_volume_down(im, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_UP:
|
||||
if (shift) {
|
||||
if (video && !repeat && down) {
|
||||
if (!repeat & down) {
|
||||
apply_orientation_transform(im,
|
||||
SC_ORIENTATION_FLIP_180);
|
||||
}
|
||||
} else if (im->kp && !paused) {
|
||||
} else if (im->kp) {
|
||||
// forward repeated events
|
||||
action_volume_up(im, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_LEFT:
|
||||
if (video && !repeat && down) {
|
||||
if (!repeat && down) {
|
||||
if (shift) {
|
||||
apply_orientation_transform(im,
|
||||
SC_ORIENTATION_FLIP_0);
|
||||
@@ -510,7 +507,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
}
|
||||
return;
|
||||
case SDLK_RIGHT:
|
||||
if (video && !repeat && down) {
|
||||
if (!repeat && down) {
|
||||
if (shift) {
|
||||
apply_orientation_transform(im,
|
||||
SC_ORIENTATION_FLIP_0);
|
||||
@@ -521,17 +518,17 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
}
|
||||
return;
|
||||
case SDLK_c:
|
||||
if (im->kp && !shift && !repeat && down && !paused) {
|
||||
if (im->kp && !shift && !repeat && down) {
|
||||
get_device_clipboard(im, SC_COPY_KEY_COPY);
|
||||
}
|
||||
return;
|
||||
case SDLK_x:
|
||||
if (im->kp && !shift && !repeat && down && !paused) {
|
||||
if (im->kp && !shift && !repeat && down) {
|
||||
get_device_clipboard(im, SC_COPY_KEY_CUT);
|
||||
}
|
||||
return;
|
||||
case SDLK_v:
|
||||
if (im->kp && !repeat && down && !paused) {
|
||||
if (im->kp && !repeat && down) {
|
||||
if (shift || im->legacy_paste) {
|
||||
// inject the text as input events
|
||||
clipboard_paste(im);
|
||||
@@ -543,27 +540,27 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
}
|
||||
return;
|
||||
case SDLK_f:
|
||||
if (video && !shift && !repeat && down) {
|
||||
if (!shift && !repeat && down) {
|
||||
sc_screen_switch_fullscreen(im->screen);
|
||||
}
|
||||
return;
|
||||
case SDLK_w:
|
||||
if (video && !shift && !repeat && down) {
|
||||
if (!shift && !repeat && down) {
|
||||
sc_screen_resize_to_fit(im->screen);
|
||||
}
|
||||
return;
|
||||
case SDLK_g:
|
||||
if (video && !shift && !repeat && down) {
|
||||
if (!shift && !repeat && down) {
|
||||
sc_screen_resize_to_pixel_perfect(im->screen);
|
||||
}
|
||||
return;
|
||||
case SDLK_i:
|
||||
if (video && !shift && !repeat && down) {
|
||||
if (!shift && !repeat && down) {
|
||||
switch_fps_counter_state(im);
|
||||
}
|
||||
return;
|
||||
case SDLK_n:
|
||||
if (control && !repeat && down && !paused) {
|
||||
if (control && !repeat && down) {
|
||||
if (shift) {
|
||||
collapse_panels(im);
|
||||
} else if (im->key_repeat == 0) {
|
||||
@@ -574,23 +571,28 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
}
|
||||
return;
|
||||
case SDLK_r:
|
||||
if (control && !shift && !repeat && down && !paused) {
|
||||
if (control && !shift && !repeat && down) {
|
||||
rotate_device(im);
|
||||
}
|
||||
return;
|
||||
case SDLK_k:
|
||||
if (control && !shift && !repeat && down && !paused
|
||||
if (control && !shift && !repeat && down
|
||||
&& im->kp && im->kp->hid) {
|
||||
// Only if the current keyboard is hid
|
||||
open_hard_keyboard_settings(im);
|
||||
}
|
||||
return;
|
||||
case SDLK_l:
|
||||
if (control && !repeat && down) {
|
||||
set_camera_torch(im, !shift);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!im->kp || paused) {
|
||||
if (!im->kp) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -635,39 +637,29 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
im->kp->ops->process_key(im->kp, &evt, ack_to_wait);
|
||||
}
|
||||
|
||||
static struct sc_position
|
||||
sc_input_manager_get_position(struct sc_input_manager *im, int32_t x,
|
||||
int32_t y) {
|
||||
if (im->mp->relative_mode) {
|
||||
// No absolute position
|
||||
return (struct sc_position) {
|
||||
.screen_size = {0, 0},
|
||||
.point = {0, 0},
|
||||
};
|
||||
}
|
||||
|
||||
return (struct sc_position) {
|
||||
.screen_size = im->screen->frame_size,
|
||||
.point = sc_screen_convert_window_to_frame_coords(im->screen, x, y),
|
||||
};
|
||||
}
|
||||
|
||||
static void
|
||||
sc_input_manager_process_mouse_motion(struct sc_input_manager *im,
|
||||
const SDL_MouseMotionEvent *event) {
|
||||
|
||||
if (event->which == SDL_TOUCH_MOUSEID) {
|
||||
// simulated from touch events, so it's a duplicate
|
||||
return;
|
||||
}
|
||||
|
||||
struct sc_mouse_motion_event evt = {
|
||||
.position = sc_input_manager_get_position(im, event->x, event->y),
|
||||
.pointer_id = im->has_secondary_click ? POINTER_ID_MOUSE
|
||||
: POINTER_ID_GENERIC_FINGER,
|
||||
.position = {
|
||||
.screen_size = im->screen->frame_size,
|
||||
.point = sc_screen_convert_window_to_frame_coords(im->screen,
|
||||
event->x,
|
||||
event->y),
|
||||
},
|
||||
.pointer_id = im->forward_all_clicks ? POINTER_ID_MOUSE
|
||||
: POINTER_ID_GENERIC_FINGER,
|
||||
.xrel = event->xrel,
|
||||
.yrel = event->yrel,
|
||||
.buttons_state =
|
||||
sc_mouse_buttons_state_from_sdl(event->state, &im->mouse_bindings),
|
||||
sc_mouse_buttons_state_from_sdl(event->state,
|
||||
im->forward_all_clicks),
|
||||
};
|
||||
|
||||
assert(im->mp->ops->process_mouse_motion);
|
||||
@@ -718,109 +710,81 @@ 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_bindings *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) {
|
||||
bool control = im->controller;
|
||||
|
||||
if (event->which == SDL_TOUCH_MOUSEID) {
|
||||
// simulated from touch events, so it's a duplicate
|
||||
return;
|
||||
}
|
||||
|
||||
bool control = im->controller;
|
||||
bool paused = im->screen->paused;
|
||||
bool down = event->type == SDL_MOUSEBUTTONDOWN;
|
||||
if (control && !paused) {
|
||||
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
|
||||
if (!im->forward_all_clicks) {
|
||||
if (control) {
|
||||
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
|
||||
|
||||
enum sc_mouse_binding binding =
|
||||
sc_input_manager_get_binding(&im->mouse_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
|
||||
if (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) {
|
||||
if (!im->mp) {
|
||||
return;
|
||||
}
|
||||
|
||||
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),
|
||||
.position = {
|
||||
.screen_size = im->screen->frame_size,
|
||||
.point = sc_screen_convert_window_to_frame_coords(im->screen,
|
||||
event->x,
|
||||
event->y),
|
||||
},
|
||||
.action = sc_action_from_sdl_mousebutton_type(event->type),
|
||||
.button = sc_mouse_button_from_sdl(event->button),
|
||||
.pointer_id = im->has_secondary_click ? POINTER_ID_MOUSE
|
||||
: POINTER_ID_GENERIC_FINGER,
|
||||
.buttons_state = sc_mouse_buttons_state_from_sdl(sdl_buttons_state,
|
||||
&im->mouse_bindings),
|
||||
.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);
|
||||
@@ -888,7 +852,11 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
|
||||
uint32_t buttons = SDL_GetMouseState(&mouse_x, &mouse_y);
|
||||
|
||||
struct sc_mouse_scroll_event evt = {
|
||||
.position = sc_input_manager_get_position(im, mouse_x, mouse_y),
|
||||
.position = {
|
||||
.screen_size = im->screen->frame_size,
|
||||
.point = sc_screen_convert_window_to_frame_coords(im->screen,
|
||||
mouse_x, mouse_y),
|
||||
},
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 18)
|
||||
.hscroll = CLAMP(event->preciseX, -1.0f, 1.0f),
|
||||
.vscroll = CLAMP(event->preciseY, -1.0f, 1.0f),
|
||||
@@ -896,8 +864,8 @@ 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 = sc_mouse_buttons_state_from_sdl(buttons,
|
||||
&im->mouse_bindings),
|
||||
.buttons_state =
|
||||
sc_mouse_buttons_state_from_sdl(buttons, im->forward_all_clicks),
|
||||
};
|
||||
|
||||
im->mp->ops->process_mouse_scroll(im->mp, &evt);
|
||||
@@ -935,10 +903,9 @@ void
|
||||
sc_input_manager_handle_event(struct sc_input_manager *im,
|
||||
const SDL_Event *event) {
|
||||
bool control = im->controller;
|
||||
bool paused = im->screen->paused;
|
||||
switch (event->type) {
|
||||
case SDL_TEXTINPUT:
|
||||
if (!im->kp || paused) {
|
||||
if (!im->kp) {
|
||||
break;
|
||||
}
|
||||
sc_input_manager_process_text_input(im, &event->text);
|
||||
@@ -950,13 +917,13 @@ sc_input_manager_handle_event(struct sc_input_manager *im,
|
||||
sc_input_manager_process_key(im, &event->key);
|
||||
break;
|
||||
case SDL_MOUSEMOTION:
|
||||
if (!im->mp || paused) {
|
||||
if (!im->mp) {
|
||||
break;
|
||||
}
|
||||
sc_input_manager_process_mouse_motion(im, &event->motion);
|
||||
break;
|
||||
case SDL_MOUSEWHEEL:
|
||||
if (!im->mp || paused) {
|
||||
if (!im->mp) {
|
||||
break;
|
||||
}
|
||||
sc_input_manager_process_mouse_wheel(im, &event->wheel);
|
||||
@@ -970,7 +937,7 @@ sc_input_manager_handle_event(struct sc_input_manager *im,
|
||||
case SDL_FINGERMOTION:
|
||||
case SDL_FINGERDOWN:
|
||||
case SDL_FINGERUP:
|
||||
if (!im->mp || paused) {
|
||||
if (!im->mp) {
|
||||
break;
|
||||
}
|
||||
sc_input_manager_process_touch(im, &event->tfinger);
|
||||
|
||||
@@ -22,12 +22,14 @@ struct sc_input_manager {
|
||||
struct sc_key_processor *kp;
|
||||
struct sc_mouse_processor *mp;
|
||||
|
||||
struct sc_mouse_bindings mouse_bindings;
|
||||
bool has_secondary_click;
|
||||
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;
|
||||
@@ -50,10 +52,10 @@ struct sc_input_manager_params {
|
||||
struct sc_key_processor *kp;
|
||||
struct sc_mouse_processor *mp;
|
||||
|
||||
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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -23,12 +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,
|
||||
.mouse_bindings = {
|
||||
.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,
|
||||
@@ -36,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,
|
||||
@@ -74,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,
|
||||
@@ -91,8 +89,6 @@ const struct scrcpy_options scrcpy_options_default = {
|
||||
.kill_adb_on_close = false,
|
||||
.camera_high_speed = false,
|
||||
.list = 0,
|
||||
.window = true,
|
||||
.mouse_hover = true,
|
||||
};
|
||||
|
||||
enum sc_orientation
|
||||
|
||||
@@ -143,33 +143,16 @@ enum sc_keyboard_input_mode {
|
||||
SC_KEYBOARD_INPUT_MODE_AUTO,
|
||||
SC_KEYBOARD_INPUT_MODE_DISABLED,
|
||||
SC_KEYBOARD_INPUT_MODE_SDK,
|
||||
SC_KEYBOARD_INPUT_MODE_UHID,
|
||||
SC_KEYBOARD_INPUT_MODE_AOA,
|
||||
SC_KEYBOARD_INPUT_MODE_UHID,
|
||||
};
|
||||
|
||||
enum sc_mouse_input_mode {
|
||||
SC_MOUSE_INPUT_MODE_AUTO,
|
||||
SC_MOUSE_INPUT_MODE_DISABLED,
|
||||
SC_MOUSE_INPUT_MODE_SDK,
|
||||
SC_MOUSE_INPUT_MODE_UHID,
|
||||
SC_MOUSE_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_bindings {
|
||||
enum sc_mouse_binding right_click;
|
||||
enum sc_mouse_binding middle_click;
|
||||
enum sc_mouse_binding click4;
|
||||
enum sc_mouse_binding click5;
|
||||
SC_MOUSE_INPUT_MODE_UHID,
|
||||
};
|
||||
|
||||
enum sc_key_inject_mode {
|
||||
@@ -186,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,
|
||||
@@ -195,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;
|
||||
@@ -225,12 +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;
|
||||
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;
|
||||
@@ -268,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;
|
||||
@@ -289,8 +279,6 @@ struct scrcpy_options {
|
||||
#define SC_OPTION_LIST_CAMERAS 0x4
|
||||
#define SC_OPTION_LIST_CAMERA_SIZES 0x8
|
||||
uint8_t list;
|
||||
bool window;
|
||||
bool mouse_hover;
|
||||
};
|
||||
|
||||
extern const struct scrcpy_options scrcpy_options_default;
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
#include "util/str.h"
|
||||
|
||||
bool
|
||||
sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket,
|
||||
const struct sc_receiver_callbacks *cbs, void *cbs_userdata) {
|
||||
sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket) {
|
||||
bool ok = sc_mutex_init(&receiver->mutex);
|
||||
if (!ok) {
|
||||
return false;
|
||||
@@ -21,10 +20,6 @@ sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket,
|
||||
receiver->acksync = NULL;
|
||||
receiver->uhid_devices = NULL;
|
||||
|
||||
assert(cbs && cbs->on_error);
|
||||
receiver->cbs = cbs;
|
||||
receiver->cbs_userdata = cbs_userdata;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -157,8 +152,6 @@ run_receiver(void *data) {
|
||||
}
|
||||
}
|
||||
|
||||
receiver->cbs->on_error(receiver, receiver->cbs_userdata);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,18 +19,10 @@ struct sc_receiver {
|
||||
|
||||
struct sc_acksync *acksync;
|
||||
struct sc_uhid_devices *uhid_devices;
|
||||
|
||||
const struct sc_receiver_callbacks *cbs;
|
||||
void *cbs_userdata;
|
||||
};
|
||||
|
||||
struct sc_receiver_callbacks {
|
||||
void (*on_error)(struct sc_receiver *receiver, void *userdata);
|
||||
};
|
||||
|
||||
bool
|
||||
sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket,
|
||||
const struct sc_receiver_callbacks *cbs, void *cbs_userdata);
|
||||
sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket);
|
||||
|
||||
void
|
||||
sc_receiver_destroy(struct sc_receiver *receiver);
|
||||
|
||||
114
app/src/scrcpy.c
114
app/src/scrcpy.c
@@ -67,17 +67,17 @@ struct scrcpy {
|
||||
#endif
|
||||
union {
|
||||
struct sc_keyboard_sdk keyboard_sdk;
|
||||
struct sc_keyboard_uhid keyboard_uhid;
|
||||
#ifdef HAVE_USB
|
||||
struct sc_keyboard_aoa keyboard_aoa;
|
||||
#endif
|
||||
struct sc_keyboard_uhid keyboard_uhid;
|
||||
};
|
||||
union {
|
||||
struct sc_mouse_sdk mouse_sdk;
|
||||
struct sc_mouse_uhid mouse_uhid;
|
||||
#ifdef HAVE_USB
|
||||
struct sc_mouse_aoa mouse_aoa;
|
||||
#endif
|
||||
struct sc_mouse_uhid mouse_uhid;
|
||||
};
|
||||
struct sc_timeout timeout;
|
||||
};
|
||||
@@ -106,6 +106,7 @@ static BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) {
|
||||
|
||||
static void
|
||||
sdl_set_hints(const char *render_driver) {
|
||||
|
||||
if (render_driver && !SDL_SetHint(SDL_HINT_RENDER_DRIVER, render_driver)) {
|
||||
LOGW("Could not set render driver");
|
||||
}
|
||||
@@ -174,9 +175,6 @@ event_loop(struct scrcpy *s) {
|
||||
case SC_EVENT_DEMUXER_ERROR:
|
||||
LOGE("Demuxer error");
|
||||
return SCRCPY_EXIT_FAILURE;
|
||||
case SC_EVENT_CONTROLLER_ERROR:
|
||||
LOGE("Controller error");
|
||||
return SCRCPY_EXIT_FAILURE;
|
||||
case SC_EVENT_RECORDER_ERROR:
|
||||
LOGE("Recorder error");
|
||||
return SCRCPY_EXIT_FAILURE;
|
||||
@@ -268,16 +266,6 @@ sc_audio_demuxer_on_ended(struct sc_demuxer *demuxer,
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
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;
|
||||
|
||||
PUSH_EVENT(SC_EVENT_CONTROLLER_ERROR);
|
||||
}
|
||||
|
||||
static void
|
||||
sc_server_on_connection_failed(struct sc_server *server, void *userdata) {
|
||||
(void) server;
|
||||
@@ -324,10 +312,6 @@ scrcpy_generate_scid(void) {
|
||||
enum scrcpy_exit_code
|
||||
scrcpy(struct scrcpy_options *options) {
|
||||
static struct scrcpy scrcpy;
|
||||
#ifndef NDEBUG
|
||||
// Detect missing initializations
|
||||
memset(&scrcpy, 42, sizeof(scrcpy));
|
||||
#endif
|
||||
struct scrcpy *s = &scrcpy;
|
||||
|
||||
// Minimal SDL initialization
|
||||
@@ -421,12 +405,6 @@ scrcpy(struct scrcpy_options *options) {
|
||||
return SCRCPY_EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (options->window) {
|
||||
// Set hints before starting the server thread to avoid race conditions
|
||||
// in SDL
|
||||
sdl_set_hints(options->render_driver);
|
||||
}
|
||||
|
||||
if (!sc_server_start(&s->server)) {
|
||||
goto end;
|
||||
}
|
||||
@@ -443,7 +421,11 @@ scrcpy(struct scrcpy_options *options) {
|
||||
assert(!options->video_playback || options->video);
|
||||
assert(!options->audio_playback || options->audio);
|
||||
|
||||
if (options->window ||
|
||||
if (options->video_playback) {
|
||||
sdl_set_hints(options->render_driver);
|
||||
}
|
||||
|
||||
if (options->video_playback ||
|
||||
(options->control && options->clipboard_autosync)) {
|
||||
// Initialize the video subsystem even if --no-video or
|
||||
// --no-video-playback is passed so that clipboard synchronization
|
||||
@@ -566,12 +548,7 @@ scrcpy(struct scrcpy_options *options) {
|
||||
struct sc_mouse_processor *mp = NULL;
|
||||
|
||||
if (options->control) {
|
||||
static const struct sc_controller_callbacks controller_cbs = {
|
||||
.on_error = sc_controller_on_error,
|
||||
};
|
||||
|
||||
if (!sc_controller_init(&s->controller, s->server.control_socket,
|
||||
&controller_cbs, NULL)) {
|
||||
if (!sc_controller_init(&s->controller, s->server.control_socket)) {
|
||||
goto end;
|
||||
}
|
||||
controller_initialized = true;
|
||||
@@ -593,7 +570,7 @@ scrcpy(struct scrcpy_options *options) {
|
||||
if (!ok) {
|
||||
LOGE("Failed to initialize USB");
|
||||
sc_acksync_destroy(&s->acksync);
|
||||
goto end;
|
||||
goto aoa_hid_end;
|
||||
}
|
||||
|
||||
assert(serial);
|
||||
@@ -601,7 +578,7 @@ scrcpy(struct scrcpy_options *options) {
|
||||
ok = sc_usb_select_device(&s->usb, serial, &usb_device);
|
||||
if (!ok) {
|
||||
sc_usb_destroy(&s->usb);
|
||||
goto end;
|
||||
goto aoa_hid_end;
|
||||
}
|
||||
|
||||
LOGI("USB device: %s (%04" PRIx16 ":%04" PRIx16 ") %s %s",
|
||||
@@ -614,7 +591,7 @@ scrcpy(struct scrcpy_options *options) {
|
||||
LOGE("Failed to connect to USB device %s", serial);
|
||||
sc_usb_destroy(&s->usb);
|
||||
sc_acksync_destroy(&s->acksync);
|
||||
goto end;
|
||||
goto aoa_hid_end;
|
||||
}
|
||||
|
||||
ok = sc_aoa_init(&s->aoa, &s->usb, &s->acksync);
|
||||
@@ -623,7 +600,7 @@ scrcpy(struct scrcpy_options *options) {
|
||||
sc_usb_disconnect(&s->usb);
|
||||
sc_usb_destroy(&s->usb);
|
||||
sc_acksync_destroy(&s->acksync);
|
||||
goto end;
|
||||
goto aoa_hid_end;
|
||||
}
|
||||
|
||||
if (use_keyboard_aoa) {
|
||||
@@ -651,18 +628,41 @@ scrcpy(struct scrcpy_options *options) {
|
||||
sc_usb_disconnect(&s->usb);
|
||||
sc_usb_destroy(&s->usb);
|
||||
sc_aoa_destroy(&s->aoa);
|
||||
goto end;
|
||||
goto aoa_hid_end;
|
||||
}
|
||||
|
||||
acksync = &s->acksync;
|
||||
|
||||
aoa_hid_initialized = true;
|
||||
|
||||
aoa_hid_end:
|
||||
if (!aoa_hid_initialized) {
|
||||
if (keyboard_aoa_initialized) {
|
||||
sc_keyboard_aoa_destroy(&s->keyboard_aoa);
|
||||
keyboard_aoa_initialized = false;
|
||||
}
|
||||
if (mouse_aoa_initialized) {
|
||||
sc_mouse_aoa_destroy(&s->mouse_aoa);
|
||||
mouse_aoa_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (use_keyboard_aoa && !keyboard_aoa_initialized) {
|
||||
LOGE("Fallback to --keyboard=sdk (--keyboard=aoa ignored)");
|
||||
options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_SDK;
|
||||
}
|
||||
|
||||
if (use_mouse_aoa && !mouse_aoa_initialized) {
|
||||
LOGE("Fallback to --keyboard=sdk (--keyboard=aoa ignored)");
|
||||
options->mouse_input_mode = SC_MOUSE_INPUT_MODE_SDK;
|
||||
}
|
||||
}
|
||||
#else
|
||||
assert(options->keyboard_input_mode != SC_KEYBOARD_INPUT_MODE_AOA);
|
||||
assert(options->mouse_input_mode != SC_MOUSE_INPUT_MODE_AOA);
|
||||
#endif
|
||||
|
||||
// keyboard_input_mode may have been reset if AOA mode failed
|
||||
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_SDK) {
|
||||
sc_keyboard_sdk_init(&s->keyboard_sdk, &s->controller,
|
||||
options->key_inject_mode,
|
||||
@@ -670,7 +670,6 @@ scrcpy(struct scrcpy_options *options) {
|
||||
kp = &s->keyboard_sdk.key_processor;
|
||||
} else if (options->keyboard_input_mode
|
||||
== SC_KEYBOARD_INPUT_MODE_UHID) {
|
||||
sc_uhid_devices_init(&s->uhid_devices);
|
||||
bool ok = sc_keyboard_uhid_init(&s->keyboard_uhid, &s->controller,
|
||||
&s->uhid_devices);
|
||||
if (!ok) {
|
||||
@@ -680,9 +679,9 @@ scrcpy(struct scrcpy_options *options) {
|
||||
kp = &s->keyboard_uhid.key_processor;
|
||||
}
|
||||
|
||||
// mouse_input_mode may have been reset if AOA mode failed
|
||||
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);
|
||||
@@ -703,20 +702,19 @@ scrcpy(struct scrcpy_options *options) {
|
||||
// There is a controller if and only if control is enabled
|
||||
assert(options->control == !!controller);
|
||||
|
||||
if (options->window) {
|
||||
if (options->video_playback) {
|
||||
const char *window_title =
|
||||
options->window_title ? options->window_title : info->device_name;
|
||||
|
||||
struct sc_screen_params screen_params = {
|
||||
.video = options->video_playback,
|
||||
.controller = controller,
|
||||
.fp = fp,
|
||||
.kp = kp,
|
||||
.mp = mp,
|
||||
.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,
|
||||
@@ -730,15 +728,12 @@ scrcpy(struct scrcpy_options *options) {
|
||||
.start_fps_counter = options->start_fps_counter,
|
||||
};
|
||||
|
||||
struct sc_frame_source *src;
|
||||
if (options->video_playback) {
|
||||
src = &s->video_decoder.frame_source;
|
||||
if (options->display_buffer) {
|
||||
sc_delay_buffer_init(&s->display_buffer,
|
||||
options->display_buffer, true);
|
||||
sc_frame_source_add_sink(src, &s->display_buffer.frame_sink);
|
||||
src = &s->display_buffer.frame_source;
|
||||
}
|
||||
struct sc_frame_source *src = &s->video_decoder.frame_source;
|
||||
if (options->display_buffer) {
|
||||
sc_delay_buffer_init(&s->display_buffer, options->display_buffer,
|
||||
true);
|
||||
sc_frame_source_add_sink(src, &s->display_buffer.frame_sink);
|
||||
src = &s->display_buffer.frame_source;
|
||||
}
|
||||
|
||||
if (!sc_screen_init(&s->screen, &screen_params)) {
|
||||
@@ -746,9 +741,7 @@ scrcpy(struct scrcpy_options *options) {
|
||||
}
|
||||
screen_initialized = true;
|
||||
|
||||
if (options->video_playback) {
|
||||
sc_frame_source_add_sink(src, &s->screen.frame_sink);
|
||||
}
|
||||
sc_frame_source_add_sink(src, &s->screen.frame_sink);
|
||||
}
|
||||
|
||||
if (options->audio_playback) {
|
||||
@@ -830,12 +823,9 @@ scrcpy(struct scrcpy_options *options) {
|
||||
ret = event_loop(s);
|
||||
LOGD("quit...");
|
||||
|
||||
if (options->video_playback) {
|
||||
// Close the window immediately on closing, because screen_destroy()
|
||||
// may only be called once the video demuxer thread is joined (it may
|
||||
// take time)
|
||||
sc_screen_hide_window(&s->screen);
|
||||
}
|
||||
// Close the window immediately on closing, because screen_destroy() may
|
||||
// only be called once the video demuxer thread is joined (it may take time)
|
||||
sc_screen_hide_window(&s->screen);
|
||||
|
||||
end:
|
||||
if (timeout_started) {
|
||||
|
||||
180
app/src/screen.c
180
app/src/screen.c
@@ -205,8 +205,6 @@ sc_screen_toggle_mouse_capture(struct sc_screen *screen) {
|
||||
|
||||
static void
|
||||
sc_screen_update_content_rect(struct sc_screen *screen) {
|
||||
assert(screen->video);
|
||||
|
||||
int dw;
|
||||
int dh;
|
||||
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
|
||||
@@ -248,8 +246,6 @@ sc_screen_update_content_rect(struct sc_screen *screen) {
|
||||
// changed, so that the content rectangle is recomputed
|
||||
static void
|
||||
sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
|
||||
assert(screen->video);
|
||||
|
||||
if (update_content_rect) {
|
||||
sc_screen_update_content_rect(screen);
|
||||
}
|
||||
@@ -259,13 +255,6 @@ sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
|
||||
(void) res; // any error already logged
|
||||
}
|
||||
|
||||
static void
|
||||
sc_screen_render_novideo(struct sc_screen *screen) {
|
||||
enum sc_display_result res =
|
||||
sc_display_render(&screen->display, NULL, SC_ORIENTATION_0);
|
||||
(void) res; // any error already logged
|
||||
}
|
||||
|
||||
#if defined(__APPLE__) || defined(__WINDOWS__)
|
||||
# define CONTINUOUS_RESIZING_WORKAROUND
|
||||
#endif
|
||||
@@ -279,8 +268,6 @@ sc_screen_render_novideo(struct sc_screen *screen) {
|
||||
static int
|
||||
event_watcher(void *data, SDL_Event *event) {
|
||||
struct sc_screen *screen = data;
|
||||
assert(screen->video);
|
||||
|
||||
if (event->type == SDL_WINDOWEVENT
|
||||
&& event->window.event == SDL_WINDOWEVENT_RESIZED) {
|
||||
// In practice, it seems to always be called from the same thread in
|
||||
@@ -339,7 +326,6 @@ sc_screen_frame_sink_close(struct sc_frame_sink *sink) {
|
||||
static bool
|
||||
sc_screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
|
||||
struct sc_screen *screen = DOWNCAST(sink);
|
||||
assert(screen->video);
|
||||
|
||||
bool previous_skipped;
|
||||
bool ok = sc_frame_buffer_push(&screen->fb, frame, &previous_skipped);
|
||||
@@ -376,11 +362,6 @@ sc_screen_init(struct sc_screen *screen,
|
||||
screen->maximized = false;
|
||||
screen->minimized = false;
|
||||
screen->mouse_capture_key_pressed = 0;
|
||||
screen->paused = false;
|
||||
screen->resume_frame = NULL;
|
||||
screen->orientation = SC_ORIENTATION_0;
|
||||
|
||||
screen->video = params->video;
|
||||
|
||||
screen->req.x = params->window_x;
|
||||
screen->req.y = params->window_y;
|
||||
@@ -398,75 +379,41 @@ sc_screen_init(struct sc_screen *screen,
|
||||
goto error_destroy_frame_buffer;
|
||||
}
|
||||
|
||||
if (screen->video) {
|
||||
screen->orientation = params->orientation;
|
||||
if (screen->orientation != SC_ORIENTATION_0) {
|
||||
LOGI("Initial display orientation set to %s",
|
||||
sc_orientation_get_name(screen->orientation));
|
||||
}
|
||||
screen->orientation = params->orientation;
|
||||
if (screen->orientation != SC_ORIENTATION_0) {
|
||||
LOGI("Initial display orientation set to %s",
|
||||
sc_orientation_get_name(screen->orientation));
|
||||
}
|
||||
|
||||
uint32_t window_flags = SDL_WINDOW_ALLOW_HIGHDPI;
|
||||
uint32_t window_flags = SDL_WINDOW_HIDDEN
|
||||
| SDL_WINDOW_RESIZABLE
|
||||
| SDL_WINDOW_ALLOW_HIGHDPI;
|
||||
if (params->always_on_top) {
|
||||
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
|
||||
}
|
||||
if (params->window_borderless) {
|
||||
window_flags |= SDL_WINDOW_BORDERLESS;
|
||||
}
|
||||
if (params->video) {
|
||||
// The window will be shown on first frame
|
||||
window_flags |= SDL_WINDOW_HIDDEN
|
||||
| SDL_WINDOW_RESIZABLE;
|
||||
}
|
||||
|
||||
const char *title = params->window_title;
|
||||
assert(title);
|
||||
|
||||
int x = SDL_WINDOWPOS_UNDEFINED;
|
||||
int y = SDL_WINDOWPOS_UNDEFINED;
|
||||
int width = 256;
|
||||
int height = 256;
|
||||
if (params->window_x != SC_WINDOW_POSITION_UNDEFINED) {
|
||||
x = params->window_x;
|
||||
}
|
||||
if (params->window_y != SC_WINDOW_POSITION_UNDEFINED) {
|
||||
y = params->window_y;
|
||||
}
|
||||
if (params->window_width) {
|
||||
width = params->window_width;
|
||||
}
|
||||
if (params->window_height) {
|
||||
height = params->window_height;
|
||||
}
|
||||
|
||||
// The window will be positioned and sized on first video frame
|
||||
screen->window = SDL_CreateWindow(title, x, y, width, height, window_flags);
|
||||
screen->window =
|
||||
SDL_CreateWindow(params->window_title, 0, 0, 0, 0, window_flags);
|
||||
if (!screen->window) {
|
||||
LOGE("Could not create window: %s", SDL_GetError());
|
||||
goto error_destroy_fps_counter;
|
||||
}
|
||||
|
||||
ok = sc_display_init(&screen->display, screen->window, params->mipmaps);
|
||||
if (!ok) {
|
||||
goto error_destroy_window;
|
||||
}
|
||||
|
||||
SDL_Surface *icon = scrcpy_icon_load();
|
||||
if (icon) {
|
||||
SDL_SetWindowIcon(screen->window, icon);
|
||||
} else if (params->video) {
|
||||
// just a warning
|
||||
LOGW("Could not load icon");
|
||||
} else {
|
||||
// without video, the icon is used as window content, it must be present
|
||||
LOGE("Could not load icon");
|
||||
goto error_destroy_fps_counter;
|
||||
}
|
||||
|
||||
SDL_Surface *icon_novideo = params->video ? NULL : icon;
|
||||
bool mipmaps = params->video && params->mipmaps;
|
||||
ok = sc_display_init(&screen->display, screen->window, icon_novideo,
|
||||
mipmaps);
|
||||
if (icon) {
|
||||
scrcpy_icon_destroy(icon);
|
||||
}
|
||||
if (!ok) {
|
||||
goto error_destroy_window;
|
||||
} else {
|
||||
LOGW("Could not load icon");
|
||||
}
|
||||
|
||||
screen->frame = av_frame_alloc();
|
||||
@@ -481,7 +428,7 @@ sc_screen_init(struct sc_screen *screen,
|
||||
.screen = screen,
|
||||
.kp = params->kp,
|
||||
.mp = params->mp,
|
||||
.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,
|
||||
@@ -490,9 +437,7 @@ sc_screen_init(struct sc_screen *screen,
|
||||
sc_input_manager_init(&screen->im, &im_params);
|
||||
|
||||
#ifdef CONTINUOUS_RESIZING_WORKAROUND
|
||||
if (screen->video) {
|
||||
SDL_AddEventWatch(event_watcher, screen);
|
||||
}
|
||||
SDL_AddEventWatch(event_watcher, screen);
|
||||
#endif
|
||||
|
||||
static const struct sc_frame_sink_ops ops = {
|
||||
@@ -507,11 +452,6 @@ sc_screen_init(struct sc_screen *screen,
|
||||
screen->open = false;
|
||||
#endif
|
||||
|
||||
if (!screen->video && sc_screen_is_relative_mode(screen)) {
|
||||
// Capture mouse immediately if video mirroring is disabled
|
||||
sc_screen_set_mouse_capture(screen, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
error_destroy_display:
|
||||
@@ -582,8 +522,6 @@ sc_screen_destroy(struct sc_screen *screen) {
|
||||
static void
|
||||
resize_for_content(struct sc_screen *screen, struct sc_size old_content_size,
|
||||
struct sc_size new_content_size) {
|
||||
assert(screen->video);
|
||||
|
||||
struct sc_size window_size = get_window_size(screen);
|
||||
struct sc_size target_size = {
|
||||
.width = (uint32_t) window_size.width * new_content_size.width
|
||||
@@ -597,8 +535,6 @@ resize_for_content(struct sc_screen *screen, struct sc_size old_content_size,
|
||||
|
||||
static void
|
||||
set_content_size(struct sc_screen *screen, struct sc_size new_content_size) {
|
||||
assert(screen->video);
|
||||
|
||||
if (!screen->fullscreen && !screen->maximized && !screen->minimized) {
|
||||
resize_for_content(screen, screen->content_size, new_content_size);
|
||||
} else if (!screen->resize_pending) {
|
||||
@@ -613,8 +549,6 @@ set_content_size(struct sc_screen *screen, struct sc_size new_content_size) {
|
||||
|
||||
static void
|
||||
apply_pending_resize(struct sc_screen *screen) {
|
||||
assert(screen->video);
|
||||
|
||||
assert(!screen->fullscreen);
|
||||
assert(!screen->maximized);
|
||||
assert(!screen->minimized);
|
||||
@@ -628,8 +562,6 @@ apply_pending_resize(struct sc_screen *screen) {
|
||||
void
|
||||
sc_screen_set_orientation(struct sc_screen *screen,
|
||||
enum sc_orientation orientation) {
|
||||
assert(screen->video);
|
||||
|
||||
if (orientation == screen->orientation) {
|
||||
return;
|
||||
}
|
||||
@@ -664,8 +596,6 @@ sc_screen_init_size(struct sc_screen *screen) {
|
||||
// recreate the texture and resize the window if the frame size has changed
|
||||
static enum sc_display_result
|
||||
prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
|
||||
assert(screen->video);
|
||||
|
||||
if (screen->frame_size.width == new_frame_size.width
|
||||
&& screen->frame_size.height == new_frame_size.height) {
|
||||
return SC_DISPLAY_RESULT_OK;
|
||||
@@ -684,12 +614,13 @@ prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_screen_apply_frame(struct sc_screen *screen) {
|
||||
assert(screen->video);
|
||||
sc_screen_update_frame(struct sc_screen *screen) {
|
||||
av_frame_unref(screen->frame);
|
||||
sc_frame_buffer_consume(&screen->fb, screen->frame);
|
||||
AVFrame *frame = screen->frame;
|
||||
|
||||
sc_fps_counter_add_rendered_frame(&screen->fps_counter);
|
||||
|
||||
AVFrame *frame = screen->frame;
|
||||
struct sc_size new_frame_size = {frame->width, frame->height};
|
||||
enum sc_display_result res = prepare_for_frame(screen, new_frame_size);
|
||||
if (res == SC_DISPLAY_RESULT_ERROR) {
|
||||
@@ -724,62 +655,8 @@ sc_screen_apply_frame(struct sc_screen *screen) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_screen_update_frame(struct sc_screen *screen) {
|
||||
assert(screen->video);
|
||||
|
||||
if (screen->paused) {
|
||||
if (!screen->resume_frame) {
|
||||
screen->resume_frame = av_frame_alloc();
|
||||
if (!screen->resume_frame) {
|
||||
LOG_OOM();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
av_frame_unref(screen->resume_frame);
|
||||
}
|
||||
sc_frame_buffer_consume(&screen->fb, screen->resume_frame);
|
||||
return true;
|
||||
}
|
||||
|
||||
av_frame_unref(screen->frame);
|
||||
sc_frame_buffer_consume(&screen->fb, screen->frame);
|
||||
return sc_screen_apply_frame(screen);
|
||||
}
|
||||
|
||||
void
|
||||
sc_screen_set_paused(struct sc_screen *screen, bool paused) {
|
||||
assert(screen->video);
|
||||
|
||||
if (!paused && !screen->paused) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
if (screen->paused && screen->resume_frame) {
|
||||
// If display screen was paused, refresh the frame immediately, even if
|
||||
// the new state is also paused.
|
||||
av_frame_free(&screen->frame);
|
||||
screen->frame = screen->resume_frame;
|
||||
screen->resume_frame = NULL;
|
||||
sc_screen_apply_frame(screen);
|
||||
}
|
||||
|
||||
if (!paused) {
|
||||
LOGI("Display screen unpaused");
|
||||
} else if (!screen->paused) {
|
||||
LOGI("Display screen paused");
|
||||
} else {
|
||||
LOGI("Display screen re-paused");
|
||||
}
|
||||
|
||||
screen->paused = paused;
|
||||
}
|
||||
|
||||
void
|
||||
sc_screen_switch_fullscreen(struct sc_screen *screen) {
|
||||
assert(screen->video);
|
||||
|
||||
uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||
if (SDL_SetWindowFullscreen(screen->window, new_mode)) {
|
||||
LOGW("Could not switch fullscreen mode: %s", SDL_GetError());
|
||||
@@ -797,8 +674,6 @@ sc_screen_switch_fullscreen(struct sc_screen *screen) {
|
||||
|
||||
void
|
||||
sc_screen_resize_to_fit(struct sc_screen *screen) {
|
||||
assert(screen->video);
|
||||
|
||||
if (screen->fullscreen || screen->maximized || screen->minimized) {
|
||||
return;
|
||||
}
|
||||
@@ -823,8 +698,6 @@ sc_screen_resize_to_fit(struct sc_screen *screen) {
|
||||
|
||||
void
|
||||
sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) {
|
||||
assert(screen->video);
|
||||
|
||||
if (screen->fullscreen || screen->minimized) {
|
||||
return;
|
||||
}
|
||||
@@ -868,13 +741,6 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
|
||||
return true;
|
||||
}
|
||||
case SDL_WINDOWEVENT:
|
||||
if (!screen->video
|
||||
&& event->window.event == SDL_WINDOWEVENT_EXPOSED) {
|
||||
sc_screen_render_novideo(screen);
|
||||
}
|
||||
|
||||
// !video implies !has_frame
|
||||
assert(screen->video || !screen->has_frame);
|
||||
if (!screen->has_frame) {
|
||||
// Do nothing
|
||||
return true;
|
||||
@@ -978,8 +844,6 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
|
||||
struct sc_point
|
||||
sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
|
||||
int32_t x, int32_t y) {
|
||||
assert(screen->video);
|
||||
|
||||
enum sc_orientation orientation = screen->orientation;
|
||||
|
||||
int32_t w = screen->content_size.width;
|
||||
|
||||
@@ -26,8 +26,6 @@ struct sc_screen {
|
||||
bool open; // track the open/close state to assert correct behavior
|
||||
#endif
|
||||
|
||||
bool video;
|
||||
|
||||
struct sc_display display;
|
||||
struct sc_input_manager im;
|
||||
struct sc_frame_buffer fb;
|
||||
@@ -66,23 +64,18 @@ struct sc_screen {
|
||||
SDL_Keycode mouse_capture_key_pressed;
|
||||
|
||||
AVFrame *frame;
|
||||
|
||||
bool paused;
|
||||
AVFrame *resume_frame;
|
||||
};
|
||||
|
||||
struct sc_screen_params {
|
||||
bool video;
|
||||
|
||||
struct sc_controller *controller;
|
||||
struct sc_file_pusher *fp;
|
||||
struct sc_key_processor *kp;
|
||||
struct sc_mouse_processor *mp;
|
||||
|
||||
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;
|
||||
@@ -142,10 +135,6 @@ void
|
||||
sc_screen_set_orientation(struct sc_screen *screen,
|
||||
enum sc_orientation orientation);
|
||||
|
||||
// set the display pause state
|
||||
void
|
||||
sc_screen_set_paused(struct sc_screen *screen, bool paused);
|
||||
|
||||
// react to SDL events
|
||||
// If this function returns false, scrcpy must exit with an error.
|
||||
bool
|
||||
|
||||
@@ -176,8 +176,6 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned flags,
|
||||
free(lpAttributeList);
|
||||
}
|
||||
|
||||
CloseHandle(pi.hThread);
|
||||
|
||||
// These handles are used by the child process, close them for this process
|
||||
if (pin) {
|
||||
CloseHandle(stdin_read_handle);
|
||||
|
||||
@@ -23,8 +23,7 @@ struct sc_key_processor {
|
||||
*/
|
||||
bool async_paste;
|
||||
|
||||
/**
|
||||
* Set by the implementation to indicate that the keyboard is HID. In
|
||||
/** Set by the implementation to indicate that the keyboard is HID. In
|
||||
* practice, it is used to react on a shortcut to open the hard keyboard
|
||||
* settings only if the keyboard is HID.
|
||||
*/
|
||||
|
||||
@@ -127,6 +127,12 @@ scrcpy_otg(struct scrcpy_options *options) {
|
||||
bool enable_mouse =
|
||||
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA;
|
||||
|
||||
// If neither --hid-keyboard or --hid-mouse is passed, enable both
|
||||
if (!enable_keyboard && !enable_mouse) {
|
||||
enable_keyboard = true;
|
||||
enable_mouse = true;
|
||||
}
|
||||
|
||||
if (enable_keyboard) {
|
||||
ok = sc_keyboard_aoa_init(&s->keyboard, &s->aoa);
|
||||
if (!ok) {
|
||||
|
||||
@@ -169,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, NULL),
|
||||
.buttons_state = sc_mouse_buttons_state_from_sdl(event->state, true),
|
||||
};
|
||||
|
||||
assert(mp->ops->process_mouse_motion);
|
||||
@@ -189,7 +189,7 @@ sc_screen_otg_process_mouse_button(struct sc_screen_otg *screen,
|
||||
.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, NULL),
|
||||
sc_mouse_buttons_state_from_sdl(sdl_buttons_state, true),
|
||||
};
|
||||
|
||||
assert(mp->ops->process_mouse_click);
|
||||
@@ -209,7 +209,7 @@ sc_screen_otg_process_mouse_wheel(struct sc_screen_otg *screen,
|
||||
.hscroll = event->x,
|
||||
.vscroll = event->y,
|
||||
.buttons_state =
|
||||
sc_mouse_buttons_state_from_sdl(sdl_buttons_state, NULL),
|
||||
sc_mouse_buttons_state_from_sdl(sdl_buttons_state, true),
|
||||
};
|
||||
|
||||
assert(mp->ops->process_mouse_scroll);
|
||||
|
||||
@@ -46,9 +46,6 @@ sc_audiobuf_read(struct sc_audiobuf *buf, void *to_, uint32_t samples_count) {
|
||||
uint32_t head = atomic_load_explicit(&buf->head, memory_order_acquire);
|
||||
|
||||
uint32_t can_read = (buf->alloc_size + head - tail) % buf->alloc_size;
|
||||
if (!can_read) {
|
||||
return 0;
|
||||
}
|
||||
if (samples_count > can_read) {
|
||||
samples_count = can_read;
|
||||
}
|
||||
@@ -89,9 +86,6 @@ sc_audiobuf_write(struct sc_audiobuf *buf, const void *from_,
|
||||
uint32_t tail = atomic_load_explicit(&buf->tail, memory_order_acquire);
|
||||
|
||||
uint32_t can_write = (buf->alloc_size + tail - head - 1) % buf->alloc_size;
|
||||
if (!can_write) {
|
||||
return 0;
|
||||
}
|
||||
if (samples_count > can_write) {
|
||||
samples_count = can_write;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
11
doc/audio.md
11
doc/audio.md
@@ -28,17 +28,10 @@ To disable only the audio playback, see [no playback](video.md#no-playback).
|
||||
|
||||
## Audio only
|
||||
|
||||
To play audio only, disable video and control:
|
||||
To play audio only, disable the video:
|
||||
|
||||
```bash
|
||||
scrcpy --no-video --no-control
|
||||
```
|
||||
|
||||
To play audio without a window:
|
||||
|
||||
```bash
|
||||
# --no-video and --no-control are implied by --no-window
|
||||
scrcpy --no-window
|
||||
scrcpy --no-video
|
||||
# interrupt with Ctrl+C
|
||||
```
|
||||
|
||||
|
||||
@@ -233,10 +233,10 @@ install` must be run as root)._
|
||||
|
||||
#### Option 2: Use prebuilt server
|
||||
|
||||
- [`scrcpy-server-v2.4`][direct-scrcpy-server]
|
||||
<sub>SHA-256: `93c272b7438605c055e127f7444064ed78fa9ca49f81156777fd201e79ce7ba3`</sub>
|
||||
- [`scrcpy-server-v2.3.1`][direct-scrcpy-server]
|
||||
<sub>SHA-256: `f6814822fc308a7a532f253485c9038183c6296a6c5df470a9e383b4f8e7605b`</sub>
|
||||
|
||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.4/scrcpy-server-v2.4
|
||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.3.1/scrcpy-server-v2.3.1
|
||||
|
||||
Download the prebuilt server somewhere, and specify its path during the Meson
|
||||
configuration:
|
||||
|
||||
@@ -67,6 +67,14 @@ computer.
|
||||
An option `--tcpip` allows to configure the connection automatically. There are
|
||||
two variants.
|
||||
|
||||
If the device (accessible at 192.168.1.1 in this example) already listens on a
|
||||
port (typically 5555) for incoming _adb_ connections, then run:
|
||||
|
||||
```bash
|
||||
scrcpy --tcpip=192.168.1.1 # default port is 5555
|
||||
scrcpy --tcpip=192.168.1.1:5555
|
||||
```
|
||||
|
||||
If _adb_ TCP/IP mode is disabled on the device (or if you don't know the IP
|
||||
address), connect the device over USB, then run:
|
||||
|
||||
@@ -77,14 +85,6 @@ scrcpy --tcpip # without arguments
|
||||
It will automatically find the device IP address and adb port, enable TCP/IP
|
||||
mode if necessary, then connect to the device before starting.
|
||||
|
||||
If the device (accessible at 192.168.1.1 in this example) already listens on a
|
||||
port (typically 5555) for incoming _adb_ connections, then run:
|
||||
|
||||
```bash
|
||||
scrcpy --tcpip=192.168.1.1 # default port is 5555
|
||||
scrcpy --tcpip=192.168.1.1:5555
|
||||
```
|
||||
|
||||
|
||||
### Manual
|
||||
|
||||
|
||||
@@ -10,34 +10,36 @@ scrcpy --no-control
|
||||
scrcpy -n # short version
|
||||
```
|
||||
|
||||
## Keyboard and mouse
|
||||
|
||||
Read [keyboard](keyboard.md) and [mouse](mouse.md).
|
||||
## Text injection preference
|
||||
|
||||
Two kinds of [events][textevents] are generated when typing text:
|
||||
- _key events_, signaling that a key is pressed or released;
|
||||
- _text events_, signaling that a text has been entered.
|
||||
|
||||
## Control only
|
||||
By default, letters are injected using key events, so that the keyboard behaves
|
||||
as expected in games (typically for WASD keys).
|
||||
|
||||
To control the device without mirroring:
|
||||
But this may [cause issues][prefertext]. If you encounter such a problem, you
|
||||
can avoid it by:
|
||||
|
||||
```bash
|
||||
scrcpy --no-video --no-audio
|
||||
scrcpy --prefer-text
|
||||
```
|
||||
|
||||
By default, mouse mode is switched to UHID if video mirroring is disabled (a
|
||||
relative mouse mode is required).
|
||||
(but this will break keyboard behavior in games)
|
||||
|
||||
To also use a UHID keyboard, set it explicitly:
|
||||
On the contrary, you could force to always inject raw key events:
|
||||
|
||||
```bash
|
||||
scrcpy --no-video --no-audio --keyboard=uhid
|
||||
scrcpy --no-video --no-audio -K # short version
|
||||
scrcpy --raw-key-events
|
||||
```
|
||||
|
||||
To use AOA instead (over USB only):
|
||||
These options have no effect on HID keyboard (all key events are sent as
|
||||
scancodes in this mode).
|
||||
|
||||
```bash
|
||||
scrcpy --no-video --no-audio --keyboard=aoa --mouse=aoa
|
||||
```
|
||||
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
|
||||
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
|
||||
|
||||
|
||||
## Copy-paste
|
||||
@@ -83,7 +85,6 @@ way as <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>).
|
||||
To disable automatic clipboard synchronization, use
|
||||
`--no-clipboard-autosync`.
|
||||
|
||||
|
||||
## Pinch-to-zoom, rotate and tilt simulation
|
||||
|
||||
To simulate "pinch-to-zoom": <kbd>Ctrl</kbd>+_click-and-move_.
|
||||
@@ -92,19 +93,37 @@ More precisely, hold down <kbd>Ctrl</kbd> while pressing the left-click button.
|
||||
Until the left-click button is released, all mouse movements scale and rotate
|
||||
the content (if supported by the app) relative to the center of the screen.
|
||||
|
||||
https://github.com/Genymobile/scrcpy/assets/543275/26c4a920-9805-43f1-8d4c-608752d04767
|
||||
|
||||
To simulate a tilt gesture: <kbd>Shift</kbd>+_click-and-move-up-or-down_.
|
||||
|
||||
https://github.com/Genymobile/scrcpy/assets/543275/1e252341-4a90-4b29-9d11-9153b324669f
|
||||
|
||||
Technically, _scrcpy_ generates additional touch events from a "virtual finger"
|
||||
at a location inverted through the center of the screen. When pressing
|
||||
<kbd>Ctrl</kbd> the _x_ and _y_ coordinates are inverted. Using <kbd>Shift</kbd>
|
||||
only inverts _x_.
|
||||
<kbd>Ctrl</kbd> the x and y coordinates are inverted. Using <kbd>Shift</kbd>
|
||||
only inverts x.
|
||||
|
||||
This only works for the default mouse mode (`--mouse=sdk`).
|
||||
|
||||
## Key repeat
|
||||
|
||||
By default, holding a key down generates repeated key events. This can cause
|
||||
performance problems in some games, where these events are useless anyway.
|
||||
|
||||
To avoid forwarding repeated key events:
|
||||
|
||||
```bash
|
||||
scrcpy --no-key-repeat
|
||||
```
|
||||
|
||||
This option has no effect on HID keyboard (key repeat is handled by Android
|
||||
directly in this mode).
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
@@ -128,3 +147,7 @@ The target directory can be changed on start:
|
||||
```bash
|
||||
scrcpy --push-target=/sdcard/Movies/
|
||||
```
|
||||
|
||||
## Physical keyboard and mouse simulation
|
||||
|
||||
See the dedicated [HID/OTG](hid-otg.md) page.
|
||||
|
||||
@@ -234,7 +234,7 @@ The video and audio streams are decoded by [FFmpeg].
|
||||
The client parses the command line arguments, then [runs one of two code
|
||||
paths][run]:
|
||||
- scrcpy in "normal" mode ([`scrcpy.c`])
|
||||
- scrcpy in [OTG mode](otg.md) ([`scrcpy_otg.c`])
|
||||
- scrcpy in [OTG mode](hid-otg.md) ([`scrcpy_otg.c`])
|
||||
|
||||
[run]: https://github.com/Genymobile/scrcpy/blob/a3cdf1a6b86ea22786e1f7d09b9c202feabc6949/app/src/main.c#L81-L82
|
||||
[`scrcpy.c`]: https://github.com/Genymobile/scrcpy/blob/a3cdf1a6b86ea22786e1f7d09b9c202feabc6949/app/src/scrcpy.c#L292-L293
|
||||
|
||||
112
doc/hid-otg.md
Normal file
112
doc/hid-otg.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# HID/OTG
|
||||
|
||||
By default, _scrcpy_ injects input events at the Android API level. As an
|
||||
alternative, when connected over USB, it is possible to send HID events, so that
|
||||
scrcpy behaves as if it was a physical keyboard and/or mouse connected to the
|
||||
Android device.
|
||||
|
||||
A special [OTG](#otg) mode allows to control the device without mirroring (and
|
||||
without USB debugging).
|
||||
|
||||
|
||||
## Physical keyboard simulation
|
||||
|
||||
By default, _scrcpy_ uses Android key or text injection. It works everywhere,
|
||||
but is limited to ASCII.
|
||||
|
||||
Instead, it can simulate a physical USB keyboard on Android to provide a better
|
||||
input experience (using [USB HID over AOAv2][hid-aoav2]): the virtual keyboard
|
||||
is disabled and it works for all characters and IME.
|
||||
|
||||
[hid-aoav2]: https://source.android.com/devices/accessories/aoa2#hid-support
|
||||
|
||||
However, it only works if the device is connected via USB.
|
||||
|
||||
Note: On Windows, it may only work in [OTG mode](#otg), not while mirroring (it
|
||||
is not possible to open a USB device if it is already open by another process
|
||||
like the _adb daemon_).
|
||||
|
||||
To enable this mode:
|
||||
|
||||
```bash
|
||||
scrcpy --hid-keyboard
|
||||
scrcpy -K # short version
|
||||
```
|
||||
|
||||
If it fails for some reason (for example because the device is not connected via
|
||||
USB), it automatically fallbacks to the default mode (with a log in the
|
||||
console). This allows using the same command line options when connected over
|
||||
USB and TCP/IP.
|
||||
|
||||
In this mode, raw key events (scancodes) are sent to the device, independently
|
||||
of the host key mapping. Therefore, if your keyboard layout does not match, it
|
||||
must be configured on the Android device, in Settings → System → Languages and
|
||||
input → [Physical keyboard].
|
||||
|
||||
This settings page can be started directly:
|
||||
|
||||
```bash
|
||||
adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS
|
||||
```
|
||||
|
||||
However, the option is only available when the HID keyboard is enabled (or when
|
||||
a physical keyboard is connected).
|
||||
|
||||
[Physical keyboard]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915
|
||||
|
||||
|
||||
## Physical mouse simulation
|
||||
|
||||
By default, _scrcpy_ uses Android mouse events injection with absolute
|
||||
coordinates. By simulating a physical mouse, a mouse pointer appears on the
|
||||
Android device, and relative mouse motion, clicks and scrolls are injected.
|
||||
|
||||
To enable this mode:
|
||||
|
||||
```bash
|
||||
scrcpy --hid-mouse
|
||||
scrcpy -M # short version
|
||||
```
|
||||
|
||||
When this mode is enabled, the computer mouse is "captured" (the mouse pointer
|
||||
disappears from the computer and appears on the Android device instead).
|
||||
|
||||
Special capture keys, either <kbd>Alt</kbd> or <kbd>Super</kbd>, toggle
|
||||
(disable or enable) the mouse capture. Use one of them to give the control of
|
||||
the mouse back to the computer.
|
||||
|
||||
|
||||
## OTG
|
||||
|
||||
It is possible to run _scrcpy_ with only physical keyboard and mouse simulation
|
||||
(HID), as if the computer keyboard and mouse were plugged directly to the device
|
||||
via an OTG cable.
|
||||
|
||||
In this mode, `adb` (USB debugging) is not necessary, and mirroring is disabled.
|
||||
|
||||
This is similar to `--hid-keyboard --hid-mouse`, but without mirroring.
|
||||
|
||||
To enable OTG mode:
|
||||
|
||||
```bash
|
||||
scrcpy --otg
|
||||
# Pass the serial if several USB devices are available
|
||||
scrcpy --otg -s 0123456789abcdef
|
||||
```
|
||||
|
||||
It is possible to enable only HID keyboard or HID mouse:
|
||||
|
||||
```bash
|
||||
scrcpy --otg --hid-keyboard # keyboard only
|
||||
scrcpy --otg --hid-mouse # mouse only
|
||||
scrcpy --otg --hid-keyboard --hid-mouse # keyboard and mouse
|
||||
# for convenience, enable both by default
|
||||
scrcpy --otg # keyboard and mouse
|
||||
```
|
||||
|
||||
Like `--hid-keyboard` and `--hid-mouse`, it only works if the device is
|
||||
connected over USB.
|
||||
|
||||
## HID/OTG issues on Windows
|
||||
|
||||
See [FAQ](/FAQ.md#hidotg-issues-on-windows).
|
||||
136
doc/keyboard.md
136
doc/keyboard.md
@@ -1,136 +0,0 @@
|
||||
# Keyboard
|
||||
|
||||
Several keyboard input modes are available:
|
||||
|
||||
- `--keyboard=sdk` (default)
|
||||
- `--keyboard=uhid` (or `-K`): simulates a physical HID keyboard using the UHID
|
||||
kernel module on the device
|
||||
- `--keyboard=aoa`: simulates a physical HID keyboard using the AOAv2 protocol
|
||||
- `--keyboard=disabled`
|
||||
|
||||
By default, `sdk` is used, but if you use scrcpy regularly, it is recommended to
|
||||
use [`uhid`](#uhid) and configure the keyboard layout once and for all.
|
||||
|
||||
|
||||
## SDK keyboard
|
||||
|
||||
In this mode (`--keyboard=sdk`, or if the parameter is omitted), keyboard input
|
||||
events are injected at the Android API level. It works everywhere, but it is
|
||||
limited to ASCII and some other characters.
|
||||
|
||||
Note that on some devices, an additional option must be enabled in developer
|
||||
options for this keyboard mode to work. See
|
||||
[prerequisites](/README.md#prerequisites).
|
||||
|
||||
Additional parameters (specific to `--keyboard=sdk`) described below allow to
|
||||
customize the behavior.
|
||||
|
||||
|
||||
### Text injection preference
|
||||
|
||||
Two kinds of [events][textevents] are generated when typing text:
|
||||
- _key events_, signaling that a key is pressed or released;
|
||||
- _text events_, signaling that a text has been entered.
|
||||
|
||||
By default, numbers and "special characters" are inserted using text events, but
|
||||
letters are injected using key events, so that the keyboard behaves as expected
|
||||
in games (typically for WASD keys).
|
||||
|
||||
But this may [cause issues][prefertext]. If you encounter such a problem, you
|
||||
can inject letters as text (or just switch to [UHID](#uhid)):
|
||||
|
||||
```bash
|
||||
scrcpy --prefer-text
|
||||
```
|
||||
|
||||
(but this will break keyboard behavior in games)
|
||||
|
||||
On the contrary, you could force to always inject raw key events:
|
||||
|
||||
```bash
|
||||
scrcpy --raw-key-events
|
||||
```
|
||||
|
||||
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
|
||||
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
|
||||
|
||||
|
||||
### Key repeat
|
||||
|
||||
By default, holding a key down generates repeated key events. Ths can cause
|
||||
performance problems in some games, where these events are useless anyway.
|
||||
|
||||
To avoid forwarding repeated key events:
|
||||
|
||||
```bash
|
||||
scrcpy --no-key-repeat
|
||||
```
|
||||
|
||||
|
||||
## Physical keyboard simulation
|
||||
|
||||
Two modes allow to simulate a physical HID keyboard on the device.
|
||||
|
||||
To work properly, it is necessary to configure (once and for all) the keyboard
|
||||
layout on the device to match that of the computer.
|
||||
|
||||
The configuration page can be opened in one of the following ways:
|
||||
- from the scrcpy window (when `uhid` or `aoa` is used), by pressing
|
||||
<kbd>MOD</kbd>+<kbd>k</kbd> (see [shortcuts](shortcuts.md))
|
||||
- from the device, in Settings → System → Languages and input → Physical
|
||||
devices
|
||||
- from a terminal on the computer, by executing `adb shell am start -a
|
||||
android.settings.HARD_KEYBOARD_SETTINGS`
|
||||
|
||||
From this configuration page, it is also possible to enable or disable on-screen
|
||||
keyboard.
|
||||
|
||||
|
||||
### UHID
|
||||
|
||||
This mode simulates a physical HID keyboard using the [UHID] kernel module on the
|
||||
device.
|
||||
|
||||
[UHID]: https://kernel.org/doc/Documentation/hid/uhid.txt
|
||||
|
||||
To enable UHID keyboard, use:
|
||||
|
||||
```bash
|
||||
scrcpy --keyboard=uhid
|
||||
scrcpy -K # short version
|
||||
```
|
||||
|
||||
Once the keyboard layout is configured (see above), it is the best mode for
|
||||
using the keyboard while mirroring:
|
||||
|
||||
- it works for all characters and IME (contrary to `--keyboard=sdk`)
|
||||
- the on-screen keyboard can be disabled (contrary to `--keyboard=sdk`)
|
||||
- it works over TCP/IP (wirelessly) (contrary to `--keyboard=aoa`)
|
||||
- there are no issues on Windows (contrary to `--keyboard=aoa`)
|
||||
|
||||
One drawback is that it may not work on old Android versions due to permission
|
||||
errors.
|
||||
|
||||
|
||||
### AOA
|
||||
|
||||
This mode simulates a physical HID keyboard using the [AOAv2] protocol.
|
||||
|
||||
[AOAv2]: https://source.android.com/devices/accessories/aoa2#hid-support
|
||||
|
||||
To enable AOA keyboard, use:
|
||||
|
||||
```bash
|
||||
scrcpy --keyboard=aoa
|
||||
```
|
||||
|
||||
Contrary to the other modes, it works at the USB level directly (so it only
|
||||
works over USB).
|
||||
|
||||
It does not use the scrcpy server, and does not require `adb` (USB debugging).
|
||||
Therefore, it is possible to control the device (but not mirror) even with USB
|
||||
debugging disabled (see [OTG](otg.md)).
|
||||
|
||||
Note: 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_).
|
||||
118
doc/mouse.md
118
doc/mouse.md
@@ -1,118 +0,0 @@
|
||||
# Mouse
|
||||
|
||||
Several mouse input modes are available:
|
||||
|
||||
- `--mouse=sdk` (default)
|
||||
- `--mouse=uhid` (or `-M`): simulates a physical HID mouse using the UHID
|
||||
kernel module on the device
|
||||
- `--mouse=aoa`: simulates a physical HID mouse using the AOAv2 protocol
|
||||
- `--mouse=disabled`
|
||||
|
||||
|
||||
## SDK mouse
|
||||
|
||||
In this mode (`--mouse=sdk`, or if the parameter is omitted), mouse input events
|
||||
are injected at the Android API level with absolute coordinates.
|
||||
|
||||
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
|
||||
|
||||
Two modes allow to simulate a physical HID mouse on the device.
|
||||
|
||||
In these modes, the computer mouse is "captured": the mouse pointer disappears
|
||||
from the computer and appears on the Android device instead.
|
||||
|
||||
Special capture keys, either <kbd>Alt</kbd> or <kbd>Super</kbd>, toggle
|
||||
(disable or enable) the mouse capture. Use one of them to give the control of
|
||||
the mouse back to the computer.
|
||||
|
||||
|
||||
### UHID
|
||||
|
||||
This mode simulates a physical HID mouse using the [UHID] kernel module on the
|
||||
device.
|
||||
|
||||
[UHID]: https://kernel.org/doc/Documentation/hid/uhid.txt
|
||||
|
||||
To enable UHID mouse, use:
|
||||
|
||||
```bash
|
||||
scrcpy --mouse=uhid
|
||||
scrcpy -M # short version
|
||||
```
|
||||
|
||||
|
||||
### AOA
|
||||
|
||||
This mode simulates a physical HID mouse using the [AOAv2] protocol.
|
||||
|
||||
[AOAv2]: https://source.android.com/devices/accessories/aoa2#hid-support
|
||||
|
||||
To enable AOA mouse, use:
|
||||
|
||||
```bash
|
||||
scrcpy --mouse=aoa
|
||||
```
|
||||
|
||||
Contrary to the other modes, it works at the USB level directly (so it only
|
||||
works over USB).
|
||||
|
||||
It does not use the scrcpy server, and does not require `adb` (USB debugging).
|
||||
Therefore, it is possible to control the device (but not mirror) even with USB
|
||||
debugging disabled (see [OTG](otg.md)).
|
||||
|
||||
Note: 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) and
|
||||
middle-click triggers HOME. In addition, the 4th click triggers APP_SWITCH and
|
||||
the 5th click expands the notification panel.
|
||||
|
||||
In AOA and UHID mouse modes, all clicks are forwarded by default.
|
||||
|
||||
The shortcuts can be configured using `--mouse-bind=xxxx` for any mouse mode.
|
||||
The argument must be exactly 4 characters, one for each secondary click:
|
||||
|
||||
```
|
||||
--mouse-bind=xxxx
|
||||
^^^^
|
||||
||||
|
||||
||| `- 5th click
|
||||
|| `-- 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 with SDK mouse
|
||||
scrcpy --mouse-bind=++++ # forward all clicks (default for AOA/UHID)
|
||||
scrcpy --mouse-bind=++bh # forward right and middle clicks,
|
||||
# use 4th and 5th for BACK and HOME
|
||||
```
|
||||
58
doc/otg.md
58
doc/otg.md
@@ -1,58 +0,0 @@
|
||||
# OTG
|
||||
|
||||
By default, _scrcpy_ injects input events at the Android API level. As an
|
||||
alternative, it is possible to send HID events, so that scrcpy behaves as if it
|
||||
was a [physical keyboard] and/or a [physical mouse] connected to the Android
|
||||
device (see [keyboard](keyboard.md) and [mouse](mouse.md)).
|
||||
|
||||
[physical keyboard]: keyboard.md#physical-keyboard-simulation
|
||||
[physical mouse]: physical-keyboard-simulation
|
||||
|
||||
A special mode (OTG) allows to control the device using AOA
|
||||
[keyboard](keyboard.md#aoa) and [mouse](mouse.md#aoa), without using _adb_ at
|
||||
all (so USB debugging is not necessary). In this mode, video and audio are
|
||||
disabled, and `--keyboard=aoa and `--mouse=aoa` are implicitly set.
|
||||
|
||||
Therefore, it is possible to run _scrcpy_ with only physical keyboard and mouse
|
||||
simulation, as if the computer keyboard and mouse were plugged directly to the
|
||||
device via an OTG cable.
|
||||
|
||||
To enable OTG mode:
|
||||
|
||||
```bash
|
||||
scrcpy --otg
|
||||
# Pass the serial if several USB devices are available
|
||||
scrcpy --otg -s 0123456789abcdef
|
||||
```
|
||||
|
||||
It is possible to disable keyboard or mouse:
|
||||
|
||||
```bash
|
||||
scrcpy --otg --keyboard=disabled
|
||||
scrcpy --otg --mouse=disabled
|
||||
```
|
||||
|
||||
It only works if the device is connected over USB.
|
||||
|
||||
## OTG issues on Windows
|
||||
|
||||
See [FAQ](/FAQ.md#otg-issues-on-windows).
|
||||
|
||||
|
||||
## Control only
|
||||
|
||||
Note that the purpose of OTG is to control the device without USB debugging
|
||||
(adb).
|
||||
|
||||
If you want to solely control the device without mirroring while USB debugging
|
||||
is enabled, then OTG mode is not necessary.
|
||||
|
||||
Instead, disable video and audio, and select UHID (or AOA):
|
||||
|
||||
```bash
|
||||
scrcpy --no-video --no-audio --keyboard=uhid --mouse=uhid
|
||||
scrcpy --no-video --no-audio -KM # short version
|
||||
scrcpy --no-video --no-audio --keyboard=aoa --mouse=aoa
|
||||
```
|
||||
|
||||
One benefit of UHID is that it also works wirelessly.
|
||||
@@ -58,10 +58,12 @@ orientation](video.md#orientation).
|
||||
|
||||
## No playback
|
||||
|
||||
To disable playback and control while recording:
|
||||
To disable playback while recording:
|
||||
|
||||
```bash
|
||||
scrcpy --no-playback --no-control --record=file.mp4
|
||||
scrcpy --no-playback --record=file.mp4
|
||||
scrcpy -Nr file.mkv
|
||||
# interrupt recording with Ctrl+C
|
||||
```
|
||||
|
||||
It is also possible to disable video and audio playback separately:
|
||||
@@ -71,13 +73,6 @@ It is also possible to disable video and audio playback separately:
|
||||
scrcpy --record=file.mkv --no-audio-playback
|
||||
```
|
||||
|
||||
To also disable the window:
|
||||
|
||||
```bash
|
||||
scrcpy --no-playback --no-window --record=file.mp4
|
||||
# interrupt recording with Ctrl+C
|
||||
```
|
||||
|
||||
## Time limit
|
||||
|
||||
To limit the recording time:
|
||||
|
||||
@@ -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._
|
||||
@@ -28,8 +28,6 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
|
||||
| Rotate display right | <kbd>MOD</kbd>+<kbd>→</kbd> _(right)_
|
||||
| Flip display horizontally | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>←</kbd> _(left)_ \| <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>→</kbd> _(right)_
|
||||
| Flip display vertically | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>↑</kbd> _(up)_ \| <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>↓</kbd> _(down)_
|
||||
| Pause or re-pause display | <kbd>MOD</kbd>+<kbd>z</kbd>
|
||||
| Unpause display | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>z</kbd>
|
||||
| Resize window to 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd>
|
||||
| Resize window to remove black borders | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Double-left-click¹_
|
||||
| Click on `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Middle-click_
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
# Window
|
||||
|
||||
## Disable window
|
||||
|
||||
To disable window (may be useful for recording or for playing audio only):
|
||||
|
||||
```bash
|
||||
scrcpy --no-window --record=file.mp4
|
||||
# Ctrl+C to interrupt
|
||||
```
|
||||
|
||||
## Title
|
||||
|
||||
By default, the window title is the device model. It can be changed:
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
|
||||
Download the [latest release]:
|
||||
|
||||
- [`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>
|
||||
- [`scrcpy-win64-v2.3.1.zip`][direct-win64] (64-bit)
|
||||
<sub>SHA-256: `f1f78ac98214078425804e524a1bed515b9d4b8a05b78d210a4ced2b910b262d`</sub>
|
||||
- [`scrcpy-win32-v2.3.1.zip`][direct-win32] (32-bit)
|
||||
<sub>SHA-256: `5dffc2d432e9b8b5b0e16f12e71428c37c70d9124cfbe7620df0b41b7efe91ff`</sub>
|
||||
|
||||
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
|
||||
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.4/scrcpy-win64-v2.4.zip
|
||||
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.4/scrcpy-win32-v2.4.zip
|
||||
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.3.1/scrcpy-win64-v2.3.1.zip
|
||||
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.3.1/scrcpy-win32-v2.3.1.zip
|
||||
|
||||
and extract it.
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
set -e
|
||||
|
||||
BUILDDIR=build-auto
|
||||
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.4/scrcpy-server-v2.4
|
||||
PREBUILT_SERVER_SHA256=93c272b7438605c055e127f7444064ed78fa9ca49f81156777fd201e79ce7ba3
|
||||
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.3.1/scrcpy-server-v2.3.1
|
||||
PREBUILT_SERVER_SHA256=f6814822fc308a7a532f253485c9038183c6296a6c5df470a9e383b4f8e7605b
|
||||
|
||||
echo "[scrcpy] Downloading prebuilt server..."
|
||||
wget "$PREBUILT_SERVER_URL" -O scrcpy-server
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
project('scrcpy', 'c',
|
||||
version: '2.4',
|
||||
version: '2.3.1',
|
||||
meson_version: '>= 0.48',
|
||||
default_options: [
|
||||
'c_std=c11',
|
||||
|
||||
50
release.mk
50
release.mk
@@ -62,38 +62,38 @@ build-server:
|
||||
meson setup "$(SERVER_BUILD_DIR)" --buildtype release -Dcompile_app=false )
|
||||
ninja -C "$(SERVER_BUILD_DIR)"
|
||||
|
||||
prepare-deps-win32:
|
||||
@app/deps/adb.sh win32
|
||||
@app/deps/sdl.sh win32
|
||||
@app/deps/ffmpeg.sh win32
|
||||
@app/deps/libusb.sh win32
|
||||
prepare-deps:
|
||||
@app/prebuilt-deps/prepare-adb.sh
|
||||
@app/prebuilt-deps/prepare-sdl.sh
|
||||
@app/prebuilt-deps/prepare-ffmpeg.sh
|
||||
@app/prebuilt-deps/prepare-libusb.sh
|
||||
|
||||
prepare-deps-win64:
|
||||
@app/deps/adb.sh win64
|
||||
@app/deps/sdl.sh win64
|
||||
@app/deps/ffmpeg.sh win64
|
||||
@app/deps/libusb.sh win64
|
||||
|
||||
build-win32: prepare-deps-win32
|
||||
build-win32: prepare-deps
|
||||
rm -rf "$(WIN32_BUILD_DIR)"
|
||||
mkdir -p "$(WIN32_BUILD_DIR)/local"
|
||||
cp -r app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-3/win32/. "$(WIN32_BUILD_DIR)/local/"
|
||||
cp -r app/prebuilt-deps/data/SDL2-2.28.5/i686-w64-mingw32/. "$(WIN32_BUILD_DIR)/local/"
|
||||
cp -r app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-Win32/. "$(WIN32_BUILD_DIR)/local/"
|
||||
meson setup "$(WIN32_BUILD_DIR)" \
|
||||
--pkg-config-path="app/deps/work/install/win32/lib/pkgconfig" \
|
||||
-Dc_args="-I$(PWD)/app/deps/work/install/win32/include" \
|
||||
-Dc_link_args="-L$(PWD)/app/deps/work/install/win32/lib" \
|
||||
--pkg-config-path="$(WIN32_BUILD_DIR)/local/lib/pkgconfig" \
|
||||
-Dc_args="-I$(PWD)/$(WIN32_BUILD_DIR)/local/include" \
|
||||
-Dc_link_args="-L$(PWD)/$(WIN32_BUILD_DIR)/local/lib" \
|
||||
--cross-file=cross_win32.txt \
|
||||
--buildtype=release --strip -Db_lto=true \
|
||||
-Dcompile_server=false \
|
||||
-Dportable=true
|
||||
ninja -C "$(WIN32_BUILD_DIR)"
|
||||
|
||||
build-win64: prepare-deps-win64
|
||||
build-win64: prepare-deps
|
||||
rm -rf "$(WIN64_BUILD_DIR)"
|
||||
mkdir -p "$(WIN64_BUILD_DIR)/local"
|
||||
cp -r app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-3/win64/. "$(WIN64_BUILD_DIR)/local/"
|
||||
cp -r app/prebuilt-deps/data/SDL2-2.28.5/x86_64-w64-mingw32/. "$(WIN64_BUILD_DIR)/local/"
|
||||
cp -r app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-x64/. "$(WIN64_BUILD_DIR)/local/"
|
||||
meson setup "$(WIN64_BUILD_DIR)" \
|
||||
--pkg-config-path="app/deps/work/install/win64/lib/pkgconfig" \
|
||||
-Dc_args="-I$(PWD)/app/deps/work/install/win64/include" \
|
||||
-Dc_link_args="-L$(PWD)/app/deps/work/install/win64/lib" \
|
||||
--pkg-config-path="$(WIN64_BUILD_DIR)/local/lib/pkgconfig" \
|
||||
-Dc_args="-I$(PWD)/$(WIN64_BUILD_DIR)/local/include" \
|
||||
-Dc_link_args="-L$(PWD)/$(WIN64_BUILD_DIR)/local/lib" \
|
||||
--cross-file=cross_win64.txt \
|
||||
--buildtype=release --strip -Db_lto=true \
|
||||
-Dcompile_server=false \
|
||||
@@ -108,8 +108,10 @@ dist-win32: build-server build-win32
|
||||
cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp app/data/icon.png "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp app/deps/work/install/win32/bin/*.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp app/deps/work/install/win32/bin/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp app/prebuilt-deps/data/platform-tools-34.0.5/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp "$(WIN32_BUILD_DIR)"/local/bin/*.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
|
||||
dist-win64: build-server build-win64
|
||||
mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)"
|
||||
@@ -119,8 +121,10 @@ dist-win64: build-server build-win64
|
||||
cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp app/data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp app/deps/work/install/win64/bin/*.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp app/deps/work/install/win64/bin/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp app/prebuilt-deps/data/platform-tools-34.0.5/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp "$(WIN64_BUILD_DIR)"/local/bin/*.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
|
||||
zip-win32: dist-win32
|
||||
cd "$(DIST)"; \
|
||||
|
||||
@@ -7,8 +7,8 @@ android {
|
||||
applicationId "com.genymobile.scrcpy"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 34
|
||||
versionCode 20400
|
||||
versionName "2.4"
|
||||
versionCode 20301
|
||||
versionName "2.3.1"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
set -e
|
||||
|
||||
SCRCPY_DEBUG=false
|
||||
SCRCPY_VERSION_NAME=2.4
|
||||
SCRCPY_VERSION_NAME=2.3.1
|
||||
|
||||
PLATFORM=${ANDROID_PLATFORM:-34}
|
||||
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-34.0.0}
|
||||
|
||||
@@ -53,6 +53,8 @@ public class CameraCapture extends SurfaceCapture {
|
||||
|
||||
private final AtomicBoolean disconnected = new AtomicBoolean();
|
||||
|
||||
private CameraCaptureSession currentSession;
|
||||
|
||||
public CameraCapture(String explicitCameraId, CameraFacing cameraFacing, Size explicitSize, int maxSize, CameraAspectRatio aspectRatio, int fps,
|
||||
boolean highSpeed) {
|
||||
this.explicitCameraId = explicitCameraId;
|
||||
@@ -127,10 +129,6 @@ public class CameraCapture extends SurfaceCapture {
|
||||
|
||||
StreamConfigurationMap configs = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
|
||||
android.util.Size[] sizes = highSpeed ? configs.getHighSpeedVideoSizes() : configs.getOutputSizes(MediaCodec.class);
|
||||
if (sizes == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Stream<android.util.Size> stream = Arrays.stream(sizes);
|
||||
if (maxSize > 0) {
|
||||
stream = stream.filter(it -> it.getWidth() <= maxSize && it.getHeight() <= maxSize);
|
||||
@@ -200,7 +198,22 @@ public class CameraCapture extends SurfaceCapture {
|
||||
public void start(Surface surface) throws IOException {
|
||||
try {
|
||||
CameraCaptureSession session = createCaptureSession(cameraDevice, surface);
|
||||
CaptureRequest request = createCaptureRequest(surface);
|
||||
CaptureRequest request = createCaptureRequest(surface, false);
|
||||
setRepeatingRequest(session, request);
|
||||
setCurrentSession(session);
|
||||
} catch (CameraAccessException | InterruptedException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
public void setTorchEnabled(boolean enabled) throws IOException {
|
||||
CameraCaptureSession session = getCurrentSession();
|
||||
if (session == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
CaptureRequest request = createCaptureRequest(session.getInputSurface(), enabled);
|
||||
setRepeatingRequest(session, request);
|
||||
} catch (CameraAccessException | InterruptedException e) {
|
||||
throw new IOException(e);
|
||||
@@ -314,9 +327,12 @@ public class CameraCapture extends SurfaceCapture {
|
||||
}
|
||||
}
|
||||
|
||||
private CaptureRequest createCaptureRequest(Surface surface) throws CameraAccessException {
|
||||
private CaptureRequest createCaptureRequest(Surface surface, boolean torch) throws CameraAccessException {
|
||||
CaptureRequest.Builder requestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
|
||||
requestBuilder.addTarget(surface);
|
||||
if (torch) {
|
||||
requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH);
|
||||
}
|
||||
|
||||
if (fps > 0) {
|
||||
requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, new Range<>(fps, fps));
|
||||
@@ -352,4 +368,12 @@ public class CameraCapture extends SurfaceCapture {
|
||||
public boolean isClosed() {
|
||||
return disconnected.get();
|
||||
}
|
||||
|
||||
private synchronized void setCurrentSession(CameraCaptureSession session) {
|
||||
currentSession = session;
|
||||
}
|
||||
|
||||
private synchronized CameraCaptureSession getCurrentSession() {
|
||||
return currentSession;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ public final class ControlMessage {
|
||||
public static final int TYPE_UHID_CREATE = 12;
|
||||
public static final int TYPE_UHID_INPUT = 13;
|
||||
public static final int TYPE_OPEN_HARD_KEYBOARD_SETTINGS = 14;
|
||||
public static final int TYPE_SET_CAMERA_TORCH = 15;
|
||||
|
||||
public static final long SEQUENCE_INVALID = 0;
|
||||
|
||||
@@ -45,6 +46,7 @@ public final class ControlMessage {
|
||||
private long sequence;
|
||||
private int id;
|
||||
private byte[] data;
|
||||
private boolean enabled;
|
||||
|
||||
private ControlMessage() {
|
||||
}
|
||||
@@ -144,6 +146,13 @@ public final class ControlMessage {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public static ControlMessage createSetCameraTorch(boolean enabled) {
|
||||
ControlMessage msg = new ControlMessage();
|
||||
msg.type = TYPE_SET_CAMERA_TORCH;
|
||||
msg.enabled = enabled;
|
||||
return msg;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
@@ -215,4 +224,8 @@ public final class ControlMessage {
|
||||
public byte[] getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ public class ControlMessageReader {
|
||||
static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 9;
|
||||
static final int UHID_CREATE_FIXED_PAYLOAD_LENGTH = 4;
|
||||
static final int UHID_INPUT_FIXED_PAYLOAD_LENGTH = 4;
|
||||
static final int CAMERA_TORCH_PAYLOAD_LENGTH = 1;
|
||||
|
||||
private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k
|
||||
|
||||
@@ -95,6 +96,9 @@ public class ControlMessageReader {
|
||||
case ControlMessage.TYPE_UHID_INPUT:
|
||||
msg = parseUhidInput();
|
||||
break;
|
||||
case ControlMessage.TYPE_SET_CAMERA_TORCH:
|
||||
msg = parseCameraTorch();
|
||||
break;
|
||||
default:
|
||||
Ln.w("Unknown event type: " + type);
|
||||
msg = null;
|
||||
@@ -245,6 +249,14 @@ public class ControlMessageReader {
|
||||
return ControlMessage.createUhidInput(id, data);
|
||||
}
|
||||
|
||||
private ControlMessage parseCameraTorch() {
|
||||
if (buffer.remaining() < CAMERA_TORCH_PAYLOAD_LENGTH) {
|
||||
return null;
|
||||
}
|
||||
boolean enabled = buffer.get() != 0;
|
||||
return ControlMessage.createSetCameraTorch(enabled);
|
||||
}
|
||||
|
||||
private static Position readPosition(ByteBuffer buffer) {
|
||||
int x = buffer.getInt();
|
||||
int y = buffer.getInt();
|
||||
|
||||
@@ -32,6 +32,7 @@ public class Controller implements AsyncProcessor {
|
||||
|
||||
private final Device device;
|
||||
private final ControlChannel controlChannel;
|
||||
private final CameraCapture cameraCapture;
|
||||
private final CleanUp cleanUp;
|
||||
private final DeviceMessageSender sender;
|
||||
private final boolean clipboardAutosync;
|
||||
@@ -46,9 +47,11 @@ public class Controller implements AsyncProcessor {
|
||||
|
||||
private boolean keepPowerModeOff;
|
||||
|
||||
public Controller(Device device, ControlChannel controlChannel, CleanUp cleanUp, boolean clipboardAutosync, boolean powerOn) {
|
||||
public Controller(Device device, ControlChannel controlChannel, CameraCapture cameraCapture, CleanUp cleanUp, boolean clipboardAutosync,
|
||||
boolean powerOn) {
|
||||
this.device = device;
|
||||
this.controlChannel = controlChannel;
|
||||
this.cameraCapture = cameraCapture;
|
||||
this.cleanUp = cleanUp;
|
||||
this.clipboardAutosync = clipboardAutosync;
|
||||
this.powerOn = powerOn;
|
||||
@@ -145,7 +148,7 @@ public class Controller implements AsyncProcessor {
|
||||
// this is expected on close
|
||||
return false;
|
||||
}
|
||||
|
||||
Ln.i("==== msg = " + msg.getType());
|
||||
switch (msg.getType()) {
|
||||
case ControlMessage.TYPE_INJECT_KEYCODE:
|
||||
if (device.supportsInputEvents()) {
|
||||
@@ -213,6 +216,10 @@ public class Controller implements AsyncProcessor {
|
||||
case ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
||||
openHardKeyboardSettings();
|
||||
break;
|
||||
case ControlMessage.TYPE_SET_CAMERA_TORCH:
|
||||
Ln.i("===");
|
||||
cameraCapture.setTorchEnabled(msg.getEnabled());
|
||||
break;
|
||||
default:
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@@ -118,16 +118,12 @@ public final class LogUtils {
|
||||
StreamConfigurationMap configs = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
|
||||
|
||||
android.util.Size[] sizes = configs.getOutputSizes(MediaCodec.class);
|
||||
if (sizes == null || sizes.length == 0) {
|
||||
builder.append("\n (none)");
|
||||
} else {
|
||||
for (android.util.Size size : sizes) {
|
||||
builder.append("\n - ").append(size.getWidth()).append('x').append(size.getHeight());
|
||||
}
|
||||
for (android.util.Size size : sizes) {
|
||||
builder.append("\n - ").append(size.getWidth()).append('x').append(size.getHeight());
|
||||
}
|
||||
|
||||
android.util.Size[] highSpeedSizes = configs.getHighSpeedVideoSizes();
|
||||
if (highSpeedSizes != null && highSpeedSizes.length > 0) {
|
||||
if (highSpeedSizes.length > 0) {
|
||||
builder.append("\n High speed capture (--camera-high-speed):");
|
||||
for (android.util.Size size : highSpeedSizes) {
|
||||
Range<Integer>[] highFpsRanges = configs.getHighSpeedVideoFpsRanges();
|
||||
|
||||
@@ -45,18 +45,18 @@ public class ScreenCapture extends SurfaceCapture implements Device.RotationList
|
||||
}
|
||||
|
||||
try {
|
||||
display = createDisplay();
|
||||
setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
|
||||
Ln.d("Display: using SurfaceControl API");
|
||||
} catch (Exception surfaceControlException) {
|
||||
Rect videoRect = screenInfo.getVideoSize().toRect();
|
||||
virtualDisplay = ServiceManager.getDisplayManager()
|
||||
.createVirtualDisplay("scrcpy", videoRect.width(), videoRect.height(), device.getDisplayId(), surface);
|
||||
Ln.d("Display: using DisplayManager API");
|
||||
} catch (Exception displayManagerException) {
|
||||
try {
|
||||
display = createDisplay();
|
||||
setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
|
||||
Ln.d("Display: using SurfaceControl API");
|
||||
} catch (Exception surfaceControlException) {
|
||||
Ln.e("Could not create display using DisplayManager", displayManagerException);
|
||||
virtualDisplay = ServiceManager.getDisplayManager()
|
||||
.createVirtualDisplay("scrcpy", videoRect.width(), videoRect.height(), device.getDisplayId(), surface);
|
||||
Ln.d("Display: using DisplayManager API");
|
||||
} catch (Exception displayManagerException) {
|
||||
Ln.e("Could not create display using SurfaceControl", surfaceControlException);
|
||||
Ln.e("Could not create display using DisplayManager", displayManagerException);
|
||||
throw new AssertionError("Could not create display");
|
||||
}
|
||||
}
|
||||
@@ -68,11 +68,6 @@ public class ScreenCapture extends SurfaceCapture implements Device.RotationList
|
||||
device.setFoldListener(null);
|
||||
if (display != null) {
|
||||
SurfaceControl.destroyDisplay(display);
|
||||
display = null;
|
||||
}
|
||||
if (virtualDisplay != null) {
|
||||
virtualDisplay.release();
|
||||
virtualDisplay = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.genymobile.scrcpy;
|
||||
|
||||
import android.os.BatteryManager;
|
||||
import android.os.Build;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -130,16 +131,6 @@ public final class Server {
|
||||
connection.sendDeviceMeta(Device.getDeviceName());
|
||||
}
|
||||
|
||||
if (control) {
|
||||
ControlChannel controlChannel = connection.getControlChannel();
|
||||
Controller controller = new Controller(device, controlChannel, cleanUp, options.getClipboardAutosync(), options.getPowerOn());
|
||||
device.setClipboardListener(text -> {
|
||||
DeviceMessage msg = DeviceMessage.createClipboard(text);
|
||||
controller.getSender().send(msg);
|
||||
});
|
||||
asyncProcessors.add(controller);
|
||||
}
|
||||
|
||||
if (audio) {
|
||||
AudioCodec audioCodec = options.getAudioCodec();
|
||||
AudioCapture audioCapture = new AudioCapture(options.getAudioSource());
|
||||
@@ -154,6 +145,7 @@ public final class Server {
|
||||
asyncProcessors.add(audioRecorder);
|
||||
}
|
||||
|
||||
CameraCapture cameraCapture = null;
|
||||
if (video) {
|
||||
Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendCodecMeta(),
|
||||
options.getSendFrameMeta());
|
||||
@@ -161,14 +153,26 @@ public final class Server {
|
||||
if (options.getVideoSource() == VideoSource.DISPLAY) {
|
||||
surfaceCapture = new ScreenCapture(device);
|
||||
} else {
|
||||
surfaceCapture = new CameraCapture(options.getCameraId(), options.getCameraFacing(), options.getCameraSize(),
|
||||
cameraCapture = new CameraCapture(options.getCameraId(), options.getCameraFacing(), options.getCameraSize(),
|
||||
options.getMaxSize(), options.getCameraAspectRatio(), options.getCameraFps(), options.getCameraHighSpeed());
|
||||
surfaceCapture = cameraCapture;
|
||||
}
|
||||
SurfaceEncoder surfaceEncoder = new SurfaceEncoder(surfaceCapture, videoStreamer, options.getVideoBitRate(), options.getMaxFps(),
|
||||
options.getVideoCodecOptions(), options.getVideoEncoder(), options.getDownsizeOnError());
|
||||
asyncProcessors.add(surfaceEncoder);
|
||||
}
|
||||
|
||||
if (control) {
|
||||
ControlChannel controlChannel = connection.getControlChannel();
|
||||
Controller controller = new Controller(
|
||||
device, controlChannel, cameraCapture, cleanUp, options.getClipboardAutosync(), options.getPowerOn());
|
||||
device.setClipboardListener(text -> {
|
||||
DeviceMessage msg = DeviceMessage.createClipboard(text);
|
||||
controller.getSender().send(msg);
|
||||
});
|
||||
asyncProcessors.add(controller);
|
||||
}
|
||||
|
||||
Completion completion = new Completion(asyncProcessors.size());
|
||||
for (AsyncProcessor asyncProcessor : asyncProcessors) {
|
||||
asyncProcessor.start((fatalError) -> {
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.genymobile.scrcpy;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.media.MediaFormat;
|
||||
import android.os.Build;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemClock;
|
||||
import android.view.Surface;
|
||||
@@ -48,7 +47,7 @@ public class SurfaceEncoder implements AsyncProcessor {
|
||||
this.downsizeOnError = downsizeOnError;
|
||||
}
|
||||
|
||||
private void streamCapture() throws IOException, ConfigurationException {
|
||||
private void streamScreen() throws IOException, ConfigurationException {
|
||||
Codec codec = streamer.getCodec();
|
||||
MediaCodec mediaCodec = createMediaCodec(codec, encoderName);
|
||||
MediaFormat format = createFormat(codec.getMimeType(), videoBitRate, maxFps, codecOptions);
|
||||
@@ -221,9 +220,6 @@ public class SurfaceEncoder implements AsyncProcessor {
|
||||
// must be present to configure the encoder, but does not impact the actual frame rate, which is variable
|
||||
format.setInteger(MediaFormat.KEY_FRAME_RATE, 60);
|
||||
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
format.setInteger(MediaFormat.KEY_COLOR_RANGE, MediaFormat.COLOR_RANGE_LIMITED);
|
||||
}
|
||||
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, DEFAULT_I_FRAME_INTERVAL);
|
||||
// display the very first frame, and recover from bad quality when no new frames
|
||||
format.setLong(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, REPEAT_FRAME_DELAY_US); // µs
|
||||
@@ -254,7 +250,7 @@ public class SurfaceEncoder implements AsyncProcessor {
|
||||
Looper.prepare();
|
||||
|
||||
try {
|
||||
streamCapture();
|
||||
streamScreen();
|
||||
} catch (ConfigurationException e) {
|
||||
// Do not print stack trace, a user-friendly error-message has already been logged
|
||||
} catch (IOException e) {
|
||||
|
||||
@@ -12,15 +12,12 @@ import java.lang.reflect.Method;
|
||||
public final class WindowManager {
|
||||
private final IInterface manager;
|
||||
private Method getRotationMethod;
|
||||
|
||||
private Method freezeRotationMethod;
|
||||
private Method freezeDisplayRotationMethod;
|
||||
private int freezeDisplayRotationMethodVersion;
|
||||
|
||||
private Method isRotationFrozenMethod;
|
||||
private Method isDisplayRotationFrozenMethod;
|
||||
private int isDisplayRotationFrozenMethodVersion;
|
||||
|
||||
private Method thawRotationMethod;
|
||||
private Method thawDisplayRotationMethod;
|
||||
private int thawDisplayRotationMethodVersion;
|
||||
|
||||
static WindowManager create() {
|
||||
IInterface manager = ServiceManager.getService("window", "android.view.IWindowManager");
|
||||
@@ -46,61 +43,50 @@ public final class WindowManager {
|
||||
return getRotationMethod;
|
||||
}
|
||||
|
||||
private Method getFreezeRotationMethod() throws NoSuchMethodException {
|
||||
if (freezeRotationMethod == null) {
|
||||
freezeRotationMethod = manager.getClass().getMethod("freezeRotation", int.class);
|
||||
}
|
||||
return freezeRotationMethod;
|
||||
}
|
||||
|
||||
// New method added by this commit:
|
||||
// <https://android.googlesource.com/platform/frameworks/base/+/90c9005e687aa0f63f1ac391adc1e8878ab31759%5E%21/>
|
||||
private Method getFreezeDisplayRotationMethod() throws NoSuchMethodException {
|
||||
if (freezeDisplayRotationMethod == null) {
|
||||
try {
|
||||
// Android 15 preview and 14 QPR3 Beta added a String caller parameter for debugging:
|
||||
// <https://android.googlesource.com/platform/frameworks/base/+/670fb7f5c0d23cf51ead25538bcb017e03ed73ac%5E%21/>
|
||||
freezeDisplayRotationMethod = manager.getClass().getMethod("freezeDisplayRotation", int.class, int.class, String.class);
|
||||
freezeDisplayRotationMethodVersion = 0;
|
||||
} catch (NoSuchMethodException e) {
|
||||
try {
|
||||
// New method added by this commit:
|
||||
// <https://android.googlesource.com/platform/frameworks/base/+/90c9005e687aa0f63f1ac391adc1e8878ab31759%5E%21/>
|
||||
freezeDisplayRotationMethod = manager.getClass().getMethod("freezeDisplayRotation", int.class, int.class);
|
||||
freezeDisplayRotationMethodVersion = 1;
|
||||
} catch (NoSuchMethodException e1) {
|
||||
freezeDisplayRotationMethod = manager.getClass().getMethod("freezeRotation", int.class);
|
||||
freezeDisplayRotationMethodVersion = 2;
|
||||
}
|
||||
}
|
||||
freezeDisplayRotationMethod = manager.getClass().getMethod("freezeDisplayRotation", int.class, int.class);
|
||||
}
|
||||
return freezeDisplayRotationMethod;
|
||||
}
|
||||
|
||||
private Method getIsRotationFrozenMethod() throws NoSuchMethodException {
|
||||
if (isRotationFrozenMethod == null) {
|
||||
isRotationFrozenMethod = manager.getClass().getMethod("isRotationFrozen");
|
||||
}
|
||||
return isRotationFrozenMethod;
|
||||
}
|
||||
|
||||
// New method added by this commit:
|
||||
// <https://android.googlesource.com/platform/frameworks/base/+/90c9005e687aa0f63f1ac391adc1e8878ab31759%5E%21/>
|
||||
private Method getIsDisplayRotationFrozenMethod() throws NoSuchMethodException {
|
||||
if (isDisplayRotationFrozenMethod == null) {
|
||||
try {
|
||||
// New method added by this commit:
|
||||
// <https://android.googlesource.com/platform/frameworks/base/+/90c9005e687aa0f63f1ac391adc1e8878ab31759%5E%21/>
|
||||
isDisplayRotationFrozenMethod = manager.getClass().getMethod("isDisplayRotationFrozen", int.class);
|
||||
isDisplayRotationFrozenMethodVersion = 0;
|
||||
} catch (NoSuchMethodException e) {
|
||||
isDisplayRotationFrozenMethod = manager.getClass().getMethod("isRotationFrozen");
|
||||
isDisplayRotationFrozenMethodVersion = 1;
|
||||
}
|
||||
isDisplayRotationFrozenMethod = manager.getClass().getMethod("isDisplayRotationFrozen", int.class);
|
||||
}
|
||||
return isDisplayRotationFrozenMethod;
|
||||
}
|
||||
|
||||
private Method getThawRotationMethod() throws NoSuchMethodException {
|
||||
if (thawRotationMethod == null) {
|
||||
thawRotationMethod = manager.getClass().getMethod("thawRotation");
|
||||
}
|
||||
return thawRotationMethod;
|
||||
}
|
||||
|
||||
// New method added by this commit:
|
||||
// <https://android.googlesource.com/platform/frameworks/base/+/90c9005e687aa0f63f1ac391adc1e8878ab31759%5E%21/>
|
||||
private Method getThawDisplayRotationMethod() throws NoSuchMethodException {
|
||||
if (thawDisplayRotationMethod == null) {
|
||||
try {
|
||||
// Android 15 preview and 14 QPR3 Beta added a String caller parameter for debugging:
|
||||
// <https://android.googlesource.com/platform/frameworks/base/+/670fb7f5c0d23cf51ead25538bcb017e03ed73ac%5E%21/>
|
||||
thawDisplayRotationMethod = manager.getClass().getMethod("thawDisplayRotation", int.class, String.class);
|
||||
thawDisplayRotationMethodVersion = 0;
|
||||
} catch (NoSuchMethodException e) {
|
||||
try {
|
||||
// New method added by this commit:
|
||||
// <https://android.googlesource.com/platform/frameworks/base/+/90c9005e687aa0f63f1ac391adc1e8878ab31759%5E%21/>
|
||||
thawDisplayRotationMethod = manager.getClass().getMethod("thawDisplayRotation", int.class);
|
||||
thawDisplayRotationMethodVersion = 1;
|
||||
} catch (NoSuchMethodException e1) {
|
||||
thawDisplayRotationMethod = manager.getClass().getMethod("thawRotation");
|
||||
thawDisplayRotationMethodVersion = 2;
|
||||
}
|
||||
}
|
||||
thawDisplayRotationMethod = manager.getClass().getMethod("thawDisplayRotation", int.class);
|
||||
}
|
||||
return thawDisplayRotationMethod;
|
||||
}
|
||||
@@ -117,21 +103,16 @@ public final class WindowManager {
|
||||
|
||||
public void freezeRotation(int displayId, int rotation) {
|
||||
try {
|
||||
Method method = getFreezeDisplayRotationMethod();
|
||||
switch (freezeDisplayRotationMethodVersion) {
|
||||
case 0:
|
||||
method.invoke(manager, displayId, rotation, "scrcpy#freezeRotation");
|
||||
break;
|
||||
case 1:
|
||||
method.invoke(manager, displayId, rotation);
|
||||
break;
|
||||
default:
|
||||
if (displayId != 0) {
|
||||
Ln.e("Secondary display rotation not supported on this device");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Method method = getFreezeDisplayRotationMethod();
|
||||
method.invoke(manager, displayId, rotation);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
if (displayId == 0) {
|
||||
Method method = getFreezeRotationMethod();
|
||||
method.invoke(manager, rotation);
|
||||
break;
|
||||
} else {
|
||||
Ln.e("Could not invoke method", e);
|
||||
}
|
||||
}
|
||||
} catch (ReflectiveOperationException e) {
|
||||
Ln.e("Could not invoke method", e);
|
||||
@@ -140,16 +121,17 @@ public final class WindowManager {
|
||||
|
||||
public boolean isRotationFrozen(int displayId) {
|
||||
try {
|
||||
Method method = getIsDisplayRotationFrozenMethod();
|
||||
switch (isDisplayRotationFrozenMethodVersion) {
|
||||
case 0:
|
||||
return (boolean) method.invoke(manager, displayId);
|
||||
default:
|
||||
if (displayId != 0) {
|
||||
Ln.e("Secondary display rotation not supported on this device");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
Method method = getIsDisplayRotationFrozenMethod();
|
||||
return (boolean) method.invoke(manager, displayId);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
if (displayId == 0) {
|
||||
Method method = getIsRotationFrozenMethod();
|
||||
return (boolean) method.invoke(manager);
|
||||
} else {
|
||||
Ln.e("Could not invoke method", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (ReflectiveOperationException e) {
|
||||
Ln.e("Could not invoke method", e);
|
||||
@@ -159,21 +141,16 @@ public final class WindowManager {
|
||||
|
||||
public void thawRotation(int displayId) {
|
||||
try {
|
||||
Method method = getThawDisplayRotationMethod();
|
||||
switch (thawDisplayRotationMethodVersion) {
|
||||
case 0:
|
||||
method.invoke(manager, displayId, "scrcpy#thawRotation");
|
||||
break;
|
||||
case 1:
|
||||
method.invoke(manager, displayId);
|
||||
break;
|
||||
default:
|
||||
if (displayId != 0) {
|
||||
Ln.e("Secondary display rotation not supported on this device");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Method method = getThawDisplayRotationMethod();
|
||||
method.invoke(manager, displayId);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
if (displayId == 0) {
|
||||
Method method = getThawRotationMethod();
|
||||
method.invoke(manager);
|
||||
break;
|
||||
} else {
|
||||
Ln.e("Could not invoke method", e);
|
||||
}
|
||||
}
|
||||
} catch (ReflectiveOperationException e) {
|
||||
Ln.e("Could not invoke method", e);
|
||||
@@ -189,10 +166,6 @@ public final class WindowManager {
|
||||
cls.getMethod("watchRotation", IRotationWatcher.class, int.class).invoke(manager, rotationWatcher, displayId);
|
||||
} catch (NoSuchMethodException e) {
|
||||
// old version
|
||||
if (displayId != 0) {
|
||||
Ln.e("Secondary display rotation not supported on this device");
|
||||
return;
|
||||
}
|
||||
cls.getMethod("watchRotation", IRotationWatcher.class).invoke(manager, rotationWatcher);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
Reference in New Issue
Block a user