Compare commits

..

10 Commits

Author SHA1 Message Date
Romain Vimont
5c2f134292 Log PTS fixing in debug mode
Audio PTS are retrieved by AudioRecord.getTimestamp(), so they do not
necessarily match exactly the number of samples (this allows to take
drift and lag into account).

In particular, the Matroska muxer uses a timebase of 1/1000 (1 ms
precision), so two consecutive timestamps in microseconds may sometimes
end up within the same millisecond, causing the warning.

Since it is "expected", lower the log level from warning to debug.
2023-11-14 18:59:23 +01:00
Romain Vimont
ef9dc85da4 Add support for RAW audio (WAV) recording
RAW audio forwarding was supported but not for recording.

Add support for recording a raw audio stream to a `.wav` file (and
`.mkv`).
2023-11-14 18:59:23 +01:00
Romain Vimont
2722865ce6 Upgrade FFmpeg build to 6.1-scrcpy-2
Use a build with WAV muxer.
2023-11-14 09:42:25 +01:00
Romain Vimont
bb0e51d6fc Fix audio PTS by the duration of 1 sample
If the difference of PTS between two consecutive blocks of audio is less
than 1 sample, then it will be considered as non-increasing by FFmpeg
muxers having a time_base of 1/sample_rate.

Increase the PTS by 1 sample instead.
2023-11-14 09:41:56 +01:00
Romain Vimont
0da94d0742 Compute PTS of intermediate blocks
If several reads are performed for a single captured audio block (e.g.
if the read size is smaller than the captured block), then the provided
timestamp was the same for all packets.

Recompute the timestamp for each of them.
2023-11-14 09:15:34 +01:00
Romain Vimont
e3520ecd50 Read audio by blocks of 1024 samples
In practice, the system captures audio samples by blocks of 1024
samples.

Remplace the hardcoded value of 5 milliseconds (240 samples), and let
AudioRecord fill the input buffer provided by MediaCodec (or by
AudioRawRecorder), with a maximum size of 1024 samples (just in case).
2023-11-14 09:11:29 +01:00
Romain Vimont
8704548274 Increase default audio buffer for FLAC
FLAC is not low latency: the default encoder produces blocks of 4096
samples, which represent ~85.333ms.

Increase the audio buffer by default so that audio playback works.
2023-11-14 09:11:29 +01:00
megapro17
a4cbc2842d Add support for FLAC audio codec
PR #4410 <#https://github.com/Genymobile/scrcpy/pull/4410>

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2023-11-14 09:11:27 +01:00
Romain Vimont
81d494c1a4 Upgrade FFmpeg build to 6.1-scrcpy
Upgrade to FFmpeg 6.1, and with FLAC support enabled.
2023-11-14 09:09:21 +01:00
Romain Vimont
387f40b168 Fix OPUS packet in an endian-independent way
Reading the header id as an int assumed that the current endianness was
little endian. Read to a byte array to remove this assumption.
2023-11-14 09:09:21 +01:00
59 changed files with 334 additions and 974 deletions

View File

@@ -1,4 +1,4 @@
# scrcpy (v2.3)
# scrcpy (v2.2)
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />

View File

@@ -19,9 +19,8 @@ _scrcpy() {
--crop=
-d --select-usb
--disable-screensaver
--display-buffer=
--display-id=
--display-orientation=
--display-buffer=
-e --select-tcpip
-f --fullscreen
--force-adb-forward
@@ -51,7 +50,6 @@ _scrcpy() {
--no-power-on
--no-video
--no-video-playback
--orientation=
--otg
-p --port=
--pause-on-exit
@@ -63,7 +61,6 @@ _scrcpy() {
-r --record=
--raw-key-events
--record-format=
--record-orientation=
--render-driver=
--require-audio
--rotation=
@@ -115,17 +112,8 @@ _scrcpy() {
COMPREPLY=($(compgen -W 'front back external' -- "$cur"))
return
;;
--orientation
--display-orientation)
COMPREPLY=($(compgen -> '0 90 180 270 flip0 flip90 flip180 flip270' -- "$cur"))
return
;;
--record-orientation)
COMPREPLY=($(compgen -> '0 90 180 270' -- "$cur"))
return
;;
--lock-video-orientation)
COMPREPLY=($(compgen -W 'unlocked initial 0 90 180 270' -- "$cur"))
COMPREPLY=($(compgen -W 'unlocked initial 0 1 2 3' -- "$cur"))
return
;;
--pause-on-exit)
@@ -144,6 +132,10 @@ _scrcpy() {
COMPREPLY=($(compgen -W 'direct3d opengl opengles2 opengles metal software' -- "$cur"))
return
;;
--rotation)
COMPREPLY=($(compgen -W '0 1 2 3' -- "$cur"))
return
;;
--shortcut-mod)
# Only auto-complete a single key
COMPREPLY=($(compgen -W 'lctrl rctrl lalt ralt lsuper rsuper' -- "$cur"))

View File

@@ -5,7 +5,7 @@ Comment=Display and control your Android device
# For some users, the PATH or ADB environment variables are set from the shell
# startup file, like .bashrc or .zshrc… Run an interactive shell to get
# environment correctly initialized.
Exec=/bin/sh -c "\\$SHELL -i -c 'scrcpy --pause-on-exit=if-error'"
Exec=/bin/sh -c "\"\\$SHELL\" -i -c scrcpy --pause-on-exit=if-error"
Icon=scrcpy
Terminal=true
Type=Application

View File

@@ -5,7 +5,7 @@ Comment=Display and control your Android device
# For some users, the PATH or ADB environment variables are set from the shell
# startup file, like .bashrc or .zshrc… Run an interactive shell to get
# environment correctly initialized.
Exec=/bin/sh -c "\\$SHELL -i -c scrcpy"
Exec=/bin/sh -c "\"\\$SHELL\" -i -c scrcpy"
Icon=scrcpy
Terminal=false
Type=Application

View File

@@ -26,9 +26,8 @@ arguments=(
'--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]'
{-d,--select-usb}'[Use USB device]'
'--disable-screensaver[Disable screensaver while scrcpy is running]'
'--display-buffer=[Add a buffering delay \(in milliseconds\) before displaying]'
'--display-id=[Specify the display id to mirror]'
'--display-orientation=[Set the initial display orientation]:orientation values:(0 90 180 270 flip0 flip90 flip180 flip270)'
'--display-buffer=[Add a buffering delay \(in milliseconds\) before displaying]'
{-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]'
@@ -41,7 +40,7 @@ arguments=(
'--list-cameras[List cameras available on the device]'
'--list-displays[List displays available on the device]'
'--list-encoders[List video and audio encoders available on the device]'
'--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 90 180 270)'
'--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 1 2 3)'
{-m,--max-size=}'[Limit both the width and height of the video to value]'
{-M,--hid-mouse}'[Simulate a physical mouse by using HID over AOAv2]'
'--max-fps=[Limit the frame rate of screen capture]'
@@ -57,7 +56,6 @@ arguments=(
'--no-power-on[Do not power on the device on start]'
'--no-video[Disable video forwarding]'
'--no-video-playback[Disable video playback]'
'--orientation=[Set the video orientation]:orientation values:(0 90 180 270 flip0 flip90 flip180 flip270)'
'--otg[Run in OTG mode \(simulating physical keyboard and mouse\)]'
{-p,--port=}'[\[port\[\:port\]\] Set the TCP port \(range\) used by the client to listen]'
'--pause-on-exit=[Make scrcpy pause before exiting]:mode:(true false if-error)'
@@ -68,9 +66,9 @@ arguments=(
{-r,--record=}'[Record screen to file]:record file:_files'
'--raw-key-events[Inject key events for all input keys, and ignore text events]'
'--record-format=[Force recording format]:format:(mp4 mkv m4a mka opus aac flac wav)'
'--record-orientation=[Set the record orientation]:orientation values:(0 90 180 270)'
'--render-driver=[Request SDL to use the given render driver]:driver name:(direct3d opengl opengles2 opengles metal software)'
'--require-audio=[Make scrcpy fail if audio is enabled but does not work]'
'--rotation=[Set the initial display rotation]:rotation values:(0 1 2 3)'
{-s,--serial=}'[The device serial number \(mandatory for multiple devices only\)]:serial:($("${ADB-adb}" devices | awk '\''$2 == "device" {print $1}'\''))'
{-S,--turn-screen-off}'[Turn the device screen off immediately]'
'--shortcut-mod=[\[key1,key2+key3,...\] Specify the modifiers to use for scrcpy shortcuts]:shortcut mod:(lctrl rctrl lalt ralt lsuper rsuper)'

View File

@@ -98,24 +98,77 @@ endif
cc = meson.get_compiler('c')
dependencies = [
dependency('libavformat', version: '>= 57.33'),
dependency('libavcodec', version: '>= 57.37'),
dependency('libavutil'),
dependency('libswresample'),
dependency('sdl2', version: '>= 2.0.5'),
]
crossbuild_windows = meson.is_cross_build() and host_machine.system() == 'windows'
if v4l2_support
dependencies += dependency('libavdevice')
endif
if not crossbuild_windows
# native build
dependencies = [
dependency('libavformat', version: '>= 57.33'),
dependency('libavcodec', version: '>= 57.37'),
dependency('libavutil'),
dependency('libswresample'),
dependency('sdl2', version: '>= 2.0.5'),
]
if v4l2_support
dependencies += dependency('libavdevice')
endif
if usb_support
dependencies += dependency('libusb-1.0')
endif
else
# cross-compile mingw32 build (from Linux to Windows)
prebuilt_sdl2 = meson.get_cross_property('prebuilt_sdl2')
sdl2_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_sdl2 + '/bin'
sdl2_lib_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_sdl2 + '/lib'
sdl2_include_dir = 'prebuilt-deps/data/' + prebuilt_sdl2 + '/include'
sdl2 = declare_dependency(
dependencies: [
cc.find_library('SDL2', dirs: sdl2_bin_dir),
cc.find_library('SDL2main', dirs: sdl2_lib_dir),
],
include_directories: include_directories(sdl2_include_dir)
)
prebuilt_ffmpeg = meson.get_cross_property('prebuilt_ffmpeg')
ffmpeg_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_ffmpeg + '/bin'
ffmpeg_include_dir = 'prebuilt-deps/data/' + prebuilt_ffmpeg + '/include'
ffmpeg = declare_dependency(
dependencies: [
cc.find_library('avcodec-60', dirs: ffmpeg_bin_dir),
cc.find_library('avformat-60', dirs: ffmpeg_bin_dir),
cc.find_library('avutil-58', dirs: ffmpeg_bin_dir),
cc.find_library('swresample-4', dirs: ffmpeg_bin_dir),
],
include_directories: include_directories(ffmpeg_include_dir)
)
prebuilt_libusb = meson.get_cross_property('prebuilt_libusb')
libusb_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_libusb + '/bin'
libusb_include_dir = 'prebuilt-deps/data/' + prebuilt_libusb + '/include'
libusb = declare_dependency(
dependencies: [
cc.find_library('msys-usb-1.0', dirs: libusb_bin_dir),
],
include_directories: include_directories(libusb_include_dir)
)
dependencies = [
ffmpeg,
sdl2,
libusb,
cc.find_library('mingw32')
]
if usb_support
dependencies += dependency('libusb-1.0')
endif
if host_machine.system() == 'windows'
dependencies += cc.find_library('mingw32')
dependencies += cc.find_library('ws2_32')
endif
@@ -236,10 +289,6 @@ if get_option('buildtype') == 'debug'
'tests/test_device_msg_deserialize.c',
'src/device_msg.c',
]],
['test_orientation', [
'tests/test_orientation.c',
'src/options.c',
]],
['test_strbuf', [
'tests/test_strbuf.c',
'src/util/strbuf.c',

View File

@@ -6,11 +6,11 @@ cd "$DIR"
mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR"
VERSION=6.1-scrcpy-3
VERSION=6.1-scrcpy-2
DEP_DIR="ffmpeg-$VERSION"
FILENAME="$DEP_DIR".7z
SHA256SUM=b646d18a3d543a4e4c46881568213499f22e4454a464e1552f03f2ac9cc3a05a
SHA256SUM=7f25f638dc24a0f5d4af07a088b6a604cf33548900bbfd2f6ce0bae050b7664d
if [[ -d "$DEP_DIR" ]]
then

View File

@@ -6,10 +6,9 @@ cd "$DIR"
mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR"
VERSION=1.0.26
DEP_DIR="libusb-$VERSION"
DEP_DIR=libusb-1.0.26
FILENAME="libusb-$VERSION-binaries.7z"
FILENAME=libusb-1.0.26-binaries.7z
SHA256SUM=9c242696342dbde9cdc47239391f71833939bf9f7aa2bbb28cdaabe890465ec5
if [[ -d "$DEP_DIR" ]]
@@ -18,22 +17,17 @@ then
exit 0
fi
get_file "https://github.com/libusb/libusb/releases/download/v$VERSION/$FILENAME" \
"$FILENAME" "$SHA256SUM"
get_file "https://github.com/libusb/libusb/releases/download/v1.0.26/$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/"
libusb-1.0.26-binaries/libusb-MinGW-Win32/bin/msys-usb-1.0.dll \
libusb-1.0.26-binaries/libusb-MinGW-Win32/include/ \
libusb-1.0.26-binaries/libusb-MinGW-x64/bin/msys-usb-1.0.dll \
libusb-1.0.26-binaries/libusb-MinGW-x64/include/
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
mv libusb-1.0.26-binaries/libusb-MinGW-Win32 .
mv libusb-1.0.26-binaries/libusb-MinGW-x64 .
rm -rf libusb-1.0.26-binaries

View File

@@ -6,11 +6,10 @@ cd "$DIR"
mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR"
VERSION=2.28.5
DEP_DIR="SDL2-$VERSION"
DEP_DIR=SDL2-2.28.4
FILENAME="SDL2-devel-$VERSION-mingw.tar.gz"
SHA256SUM=3c0c655c2ebf67cad48fead72761d1601740ded30808952c3274ba223d226c21
FILENAME=SDL2-devel-2.28.4-mingw.tar.gz
SHA256SUM=779d091072cf97291f80030f5232d97aa3d48ab0f2c14fe0b9d9a33c593cdc35
if [[ -d "$DEP_DIR" ]]
then
@@ -18,8 +17,7 @@ then
exit 0
fi
get_file "https://github.com/libsdl-org/SDL/releases/download/release-$VERSION/$FILENAME" \
"$FILENAME" "$SHA256SUM"
get_file "https://libsdl.org/release/$FILENAME" "$FILENAME" "$SHA256SUM"
mkdir "$DEP_DIR"
cd "$DEP_DIR"

View File

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

View File

@@ -26,7 +26,7 @@ Encode the audio at the given bit rate, expressed in bits/s. Unit suffixes are s
Default is 128K (128000).
.TP
.BI "\-\-audio\-buffer " ms
.BI "\-\-audio\-buffer ms
Configure the audio buffering delay (in milliseconds).
Lower values decrease the latency, but increase the likelyhood of buffer underrun (causing audio glitches).
@@ -62,7 +62,7 @@ Select the audio source (output or mic).
Default is output.
.TP
.BI "\-\-audio\-output\-buffer " ms
.BI "\-\-audio\-output\-buffer ms
Configure the size of the SDL audio output buffer (in milliseconds).
If you get "robotic" audio playback, you should test with a higher value (10). Do not change this setting otherwise.
@@ -127,12 +127,6 @@ Also see \fB\-e\fR (\fB\-\-select\-tcpip\fR).
.BI "\-\-disable-screensaver"
Disable screensaver while scrcpy is running.
.TP
.BI "\-\-display\-buffer " ms
Add a buffering delay (in milliseconds) before displaying. This increases latency to compensate for jitter.
Default is 0 (no buffering).
.TP
.BI "\-\-display\-id " id
Specify the device display id to mirror.
@@ -142,12 +136,10 @@ The available display ids can be listed by \fB\-\-list\-displays\fR.
Default is 0.
.TP
.BI "\-\-display\-orientation " value
Set the initial display orientation.
.BI "\-\-display\-buffer ms
Add a buffering delay (in milliseconds) before displaying. This increases latency to compensate for jitter.
Possible values are 0, 90, 180, 270, flip0, flip90, flip180 and flip270. The number represents the clockwise rotation in degrees; the "flip" keyword applies a horizontal flip before the rotation.
Default is 0.
Default is 0 (no buffering).
.TP
.B \-e, \-\-select\-tcpip
@@ -215,9 +207,7 @@ List displays available on the device.
.TP
\fB\-\-lock\-video\-orientation\fR[=\fIvalue\fR]
Lock capture video orientation to \fIvalue\fR.
Possible values are "unlocked", "initial" (locked to the initial orientation), 0, 90, 180, and 270. The values represent the clockwise rotation from the natural device orientation, in degrees.
Lock video orientation to \fIvalue\fR. Possible values are "unlocked", "initial" (locked to the initial orientation), 0, 1, 2 and 3. Natural device orientation is 0, and each increment adds a 90 degrees rotation counterclockwise.
Default is "unlocked".
@@ -299,10 +289,6 @@ Disable video forwarding.
.B \-\-no\-video\-playback
Disable video playback on the computer.
.TP
.BI "\-\-orientation " value
Same as --display-orientation=value --record-orientation=value.
.TP
.B \-\-otg
Run in OTG mode: simulate physical keyboard and mouse, as if the computer keyboard and mouse were plugged directly to the device via an OTG cable.
@@ -371,14 +357,6 @@ Inject key events for all input keys, and ignore text events.
.BI "\-\-record\-format " format
Force recording format (mp4, mkv, m4a, mka, opus, aac, flac or wav).
.TP
.BI "\-\-record\-orientation " value
Set the record orientation.
Possible values are 0, 90, 180 and 270. The number represents the clockwise rotation in degrees.
Default is 0.
.TP
.BI "\-\-render\-driver " name
Request SDL to use the given render driver (this is just a hint).
@@ -391,6 +369,10 @@ Supported names are currently "direct3d", "opengl", "opengles2", "opengles", "me
.B \-\-require\-audio
By default, scrcpy mirrors only the video if audio capture fails on the device. This option makes scrcpy fail if audio is enabled but does not work.
.TP
.BI "\-\-rotation " value
Set the initial display rotation. Possibles values are 0, 1, 2 and 3. Each increment adds a 90 degrees rotation counterclockwise.
.TP
.BI "\-s, \-\-serial " number
The device serial number. Mandatory only if several devices are connected to adb.
@@ -552,14 +534,6 @@ Rotate display left
.B MOD+Right
Rotate display right
.TP
.B MOD+Shift+Left, MOD+Shift+Right
Flip display horizontally
.TP
.B MOD+Shift+Up, MOD+Shift+Down
Flip display vertically
.TP
.B MOD+g
Resize window to 1:1 (pixel\-perfect)

View File

@@ -90,9 +90,6 @@ enum {
OPT_CAMERA_AR,
OPT_CAMERA_FPS,
OPT_CAMERA_HIGH_SPEED,
OPT_DISPLAY_ORIENTATION,
OPT_RECORD_ORIENTATION,
OPT_ORIENTATION,
};
struct sc_option {
@@ -295,14 +292,6 @@ static const struct sc_option options[] = {
.longopt = "display",
.argdesc = "id",
},
{
.longopt_id = OPT_DISPLAY_BUFFER,
.longopt = "display-buffer",
.argdesc = "ms",
.text = "Add a buffering delay (in milliseconds) before displaying. "
"This increases latency to compensate for jitter.\n"
"Default is 0 (no buffering).",
},
{
.longopt_id = OPT_DISPLAY_ID,
.longopt = "display-id",
@@ -313,15 +302,12 @@ static const struct sc_option options[] = {
"Default is 0.",
},
{
.longopt_id = OPT_DISPLAY_ORIENTATION,
.longopt = "display-orientation",
.argdesc = "value",
.text = "Set the initial display orientation.\n"
"Possible values are 0, 90, 180, 270, flip0, flip90, flip180 "
"and flip270. The number represents the clockwise rotation "
"in degrees; the \"flip\" keyword applies a horizontal flip "
"before the rotation.\n"
"Default is 0.",
.longopt_id = OPT_DISPLAY_BUFFER,
.longopt = "display-buffer",
.argdesc = "ms",
.text = "Add a buffering delay (in milliseconds) before displaying. "
"This increases latency to compensate for jitter.\n"
"Default is 0 (no buffering).",
},
{
.shortopt = 'e',
@@ -413,11 +399,11 @@ static const struct sc_option options[] = {
.longopt = "lock-video-orientation",
.argdesc = "value",
.optional_arg = true,
.text = "Lock capture video orientation to value.\n"
.text = "Lock video orientation to value.\n"
"Possible values are \"unlocked\", \"initial\" (locked to the "
"initial orientation), 0, 90, 180 and 270. The values "
"represent the clockwise rotation from the natural device "
"orientation, in degrees.\n"
"initial orientation), 0, 1, 2 and 3. Natural device "
"orientation is 0, and each increment adds a 90 degrees "
"rotation counterclockwise.\n"
"Default is \"unlocked\".\n"
"Passing the option without argument is equivalent to passing "
"\"initial\".",
@@ -526,13 +512,6 @@ static const struct sc_option options[] = {
.longopt = "no-video-playback",
.text = "Disable video playback on the computer.",
},
{
.longopt_id = OPT_ORIENTATION,
.longopt = "orientation",
.argdesc = "value",
.text = "Same as --display-orientation=value "
"--record-orientation=value.",
},
{
.longopt_id = OPT_OTG,
.longopt = "otg",
@@ -618,15 +597,6 @@ static const struct sc_option options[] = {
.text = "Force recording format (mp4, mkv, m4a, mka, opus, aac, flac "
"or wav).",
},
{
.longopt_id = OPT_RECORD_ORIENTATION,
.longopt = "record-orientation",
.argdesc = "value",
.text = "Set the record orientation.\n"
"Possible values are 0, 90, 180 and 270. The number represents "
"the clockwise rotation in degrees.\n"
"Default is 0.",
},
{
.longopt_id = OPT_RENDER_DRIVER,
.longopt = "render-driver",
@@ -645,10 +615,12 @@ static const struct sc_option options[] = {
"is enabled but does not work."
},
{
// deprecated
.longopt_id = OPT_ROTATION,
.longopt = "rotation",
.argdesc = "value",
.text = "Set the initial display rotation.\n"
"Possible values are 0, 1, 2 and 3. Each increment adds a 90 "
"degrees rotation counterclockwise.",
},
{
.shortopt = 's',
@@ -852,14 +824,6 @@ static const struct sc_shortcut shortcuts[] = {
.shortcuts = { "MOD+Right" },
.text = "Rotate display right",
},
{
.shortcuts = { "MOD+Shift+Left", "MOD+Shift+Right" },
.text = "Flip display horizontally",
},
{
.shortcuts = { "MOD+Shift+Up", "MOD+Shift+Down" },
.text = "Flip display vertically",
},
{
.shortcuts = { "MOD+g" },
.text = "Resize window to 1:1 (pixel-perfect)",
@@ -1418,50 +1382,15 @@ parse_lock_video_orientation(const char *s,
return true;
}
if (!strcmp(s, "0")) {
*lock_mode = SC_LOCK_VIDEO_ORIENTATION_0;
return true;
long value;
bool ok = parse_integer_arg(s, &value, false, 0, 3,
"lock video orientation");
if (!ok) {
return false;
}
if (!strcmp(s, "90")) {
*lock_mode = SC_LOCK_VIDEO_ORIENTATION_90;
return true;
}
if (!strcmp(s, "180")) {
*lock_mode = SC_LOCK_VIDEO_ORIENTATION_180;
return true;
}
if (!strcmp(s, "270")) {
*lock_mode = SC_LOCK_VIDEO_ORIENTATION_270;
return true;
}
if (!strcmp(s, "1")) {
LOGW("--lock-video-orientation=1 is deprecated, use "
"--lock-video-orientation=270 instead.");
*lock_mode = SC_LOCK_VIDEO_ORIENTATION_270;
return true;
}
if (!strcmp(s, "2")) {
LOGW("--lock-video-orientation=2 is deprecated, use "
"--lock-video-orientation=180 instead.");
*lock_mode = SC_LOCK_VIDEO_ORIENTATION_180;
return true;
}
if (!strcmp(s, "3")) {
LOGW("--lock-video-orientation=3 is deprecated, use "
"--lock-video-orientation=90 instead.");
*lock_mode = SC_LOCK_VIDEO_ORIENTATION_90;
return true;
}
LOGE("Unsupported --lock-video-orientation value: %s (expected initial, "
"unlocked, 0, 90, 180 or 270).", s);
return false;
*lock_mode = (enum sc_lock_video_orientation) value;
return true;
}
static bool
@@ -1476,45 +1405,6 @@ parse_rotation(const char *s, uint8_t *rotation) {
return true;
}
static bool
parse_orientation(const char *s, enum sc_orientation *orientation) {
if (!strcmp(s, "0")) {
*orientation = SC_ORIENTATION_0;
return true;
}
if (!strcmp(s, "90")) {
*orientation = SC_ORIENTATION_90;
return true;
}
if (!strcmp(s, "180")) {
*orientation = SC_ORIENTATION_180;
return true;
}
if (!strcmp(s, "270")) {
*orientation = SC_ORIENTATION_270;
return true;
}
if (!strcmp(s, "flip0")) {
*orientation = SC_ORIENTATION_FLIP_0;
return true;
}
if (!strcmp(s, "flip90")) {
*orientation = SC_ORIENTATION_FLIP_90;
return true;
}
if (!strcmp(s, "flip180")) {
*orientation = SC_ORIENTATION_FLIP_180;
return true;
}
if (!strcmp(s, "flip270")) {
*orientation = SC_ORIENTATION_FLIP_270;
return true;
}
LOGE("Unsupported orientation: %s (expected 0, 90, 180, 270, flip0, "
"flip90, flip180 or flip270)", optarg);
return false;
}
static bool
parse_window_position(const char *s, int16_t *position) {
// special value for "auto"
@@ -1750,8 +1640,7 @@ static bool
parse_record_format(const char *optarg, enum sc_record_format *format) {
enum sc_record_format fmt = get_record_format(optarg);
if (!fmt) {
LOGE("Unsupported record format: %s (expected mp4, mkv, m4a, mka, "
"opus, aac, flac or wav)", optarg);
LOGE("Unsupported format: %s (expected mp4 or mkv)", optarg);
return false;
}
@@ -1821,8 +1710,7 @@ parse_audio_codec(const char *optarg, enum sc_codec *codec) {
*codec = SC_CODEC_RAW;
return true;
}
LOGE("Unsupported audio codec: %s (expected opus, aac, flac or raw)",
optarg);
LOGE("Unsupported audio codec: %s (expected opus, aac, flac or raw)", optarg);
return false;
}
@@ -2118,51 +2006,10 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
opts->key_inject_mode = SC_KEY_INJECT_MODE_RAW;
break;
case OPT_ROTATION:
LOGW("--rotation is deprecated, use --display-orientation "
"instead.");
uint8_t rotation;
if (!parse_rotation(optarg, &rotation)) {
return false;
}
assert(rotation <= 3);
switch (rotation) {
case 0:
opts->display_orientation = SC_ORIENTATION_0;
break;
case 1:
// rotation 1 was 90° counterclockwise, but orientation
// is expressed clockwise
opts->display_orientation = SC_ORIENTATION_270;
break;
case 2:
opts->display_orientation = SC_ORIENTATION_180;
break;
case 3:
// rotation 3 was 270° counterclockwise, but orientation
// is expressed clockwise
opts->display_orientation = SC_ORIENTATION_90;
break;
}
break;
case OPT_DISPLAY_ORIENTATION:
if (!parse_orientation(optarg, &opts->display_orientation)) {
if (!parse_rotation(optarg, &opts->rotation)) {
return false;
}
break;
case OPT_RECORD_ORIENTATION:
if (!parse_orientation(optarg, &opts->record_orientation)) {
return false;
}
break;
case OPT_ORIENTATION: {
enum sc_orientation orientation;
if (!parse_orientation(optarg, &orientation)) {
return false;
}
opts->display_orientation = orientation;
opts->record_orientation = orientation;
break;
}
case OPT_RENDER_DRIVER:
opts->render_driver = optarg;
break;
@@ -2529,15 +2376,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
}
}
if (opts->record_orientation != SC_ORIENTATION_0) {
if (sc_orientation_is_mirror(opts->record_orientation)) {
LOGE("Record orientation only supports rotation, not "
"flipping: %s",
sc_orientation_get_name(opts->record_orientation));
return false;
}
}
if (opts->video
&& sc_record_format_is_audio_only(opts->record_format)) {
LOGE("Audio container does not support video stream");

View File

@@ -3,9 +3,7 @@
#include "config.h"
#include <libavcodec/version.h>
#include <libavformat/version.h>
#include <libavutil/version.h>
#include <SDL2/SDL_version.h>
#ifndef __WIN32
@@ -52,15 +50,6 @@
# define SCRCPY_LAVU_HAS_CHLAYOUT
#endif
// In ffmpeg/doc/APIchanges:
// 2023-10-06 - 5432d2aacad - lavc 60.15.100 - avformat.h
// Deprecate AVFormatContext.{nb_,}side_data, av_stream_add_side_data(),
// av_stream_new_side_data(), and av_stream_get_side_data(). Side data fields
// from AVFormatContext.codecpar should be used from now on.
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(60, 15, 100)
# define SCRCPY_LAVC_HAS_CODECPAR_CODEC_SIDEDATA
#endif
#if SDL_VERSION_ATLEAST(2, 0, 6)
// <https://github.com/libsdl-org/SDL/commit/d7a318de563125e5bb465b1000d6bc9576fbc6fc>
# define SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS

View File

@@ -227,9 +227,8 @@ run_demuxer(void *data) {
}
// Config packets must be merged with the next non-config packet only for
// H.26x
bool must_merge_config_packet = raw_codec_id == SC_CODEC_ID_H264
|| raw_codec_id == SC_CODEC_ID_H265;
// video streams
bool must_merge_config_packet = codec->type == AVMEDIA_TYPE_VIDEO;
struct sc_packet_merger merger;

View File

@@ -234,7 +234,7 @@ sc_display_update_texture(struct sc_display *display, const AVFrame *frame) {
enum sc_display_result
sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
enum sc_orientation orientation) {
unsigned rotation) {
SDL_RenderClear(display->renderer);
if (display->pending.flags) {
@@ -247,33 +247,33 @@ sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
SDL_Renderer *renderer = display->renderer;
SDL_Texture *texture = display->texture;
if (orientation == SC_ORIENTATION_0) {
if (rotation == 0) {
int ret = SDL_RenderCopy(renderer, texture, NULL, geometry);
if (ret) {
LOGE("Could not render texture: %s", SDL_GetError());
return SC_DISPLAY_RESULT_ERROR;
}
} else {
unsigned cw_rotation = sc_orientation_get_rotation(orientation);
// rotation in RenderCopyEx() is clockwise, while screen->rotation is
// counterclockwise (to be consistent with --lock-video-orientation)
int cw_rotation = (4 - rotation) % 4;
double angle = 90 * cw_rotation;
const SDL_Rect *dstrect = NULL;
SDL_Rect rect;
if (sc_orientation_is_swap(orientation)) {
if (rotation & 1) {
rect.x = geometry->x + (geometry->w - geometry->h) / 2;
rect.y = geometry->y + (geometry->h - geometry->w) / 2;
rect.w = geometry->h;
rect.h = geometry->w;
dstrect = &rect;
} else {
assert(rotation == 2);
dstrect = geometry;
}
SDL_RendererFlip flip = sc_orientation_is_mirror(orientation)
? SDL_FLIP_HORIZONTAL : 0;
int ret = SDL_RenderCopyEx(renderer, texture, NULL, dstrect, angle,
NULL, flip);
NULL, 0);
if (ret) {
LOGE("Could not render texture: %s", SDL_GetError());
return SC_DISPLAY_RESULT_ERROR;

View File

@@ -9,7 +9,6 @@
#include "coords.h"
#include "opengl.h"
#include "options.h"
#ifdef __APPLE__
# define SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
@@ -55,6 +54,6 @@ sc_display_update_texture(struct sc_display *display, const AVFrame *frame);
enum sc_display_result
sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
enum sc_orientation orientation);
unsigned rotation);
#endif

View File

@@ -293,11 +293,15 @@ rotate_device(struct sc_controller *controller) {
}
static void
apply_orientation_transform(struct sc_screen *screen,
enum sc_orientation transform) {
enum sc_orientation new_orientation =
sc_orientation_apply(screen->orientation, transform);
sc_screen_set_orientation(screen, new_orientation);
rotate_client_left(struct sc_screen *screen) {
unsigned new_rotation = (screen->rotation + 1) % 4;
sc_screen_set_rotation(screen, new_rotation);
}
static void
rotate_client_right(struct sc_screen *screen) {
unsigned new_rotation = (screen->rotation + 3) % 4;
sc_screen_set_rotation(screen, new_rotation);
}
static void
@@ -417,47 +421,25 @@ sc_input_manager_process_key(struct sc_input_manager *im,
}
return;
case SDLK_DOWN:
if (shift) {
if (!repeat & down) {
apply_orientation_transform(im->screen,
SC_ORIENTATION_FLIP_180);
}
} else if (controller) {
if (controller && !shift) {
// forward repeated events
action_volume_down(controller, action);
}
return;
case SDLK_UP:
if (shift) {
if (!repeat & down) {
apply_orientation_transform(im->screen,
SC_ORIENTATION_FLIP_180);
}
} else if (controller) {
if (controller && !shift) {
// forward repeated events
action_volume_up(controller, action);
}
return;
case SDLK_LEFT:
if (!repeat && down) {
if (shift) {
apply_orientation_transform(im->screen,
SC_ORIENTATION_FLIP_0);
} else {
apply_orientation_transform(im->screen,
SC_ORIENTATION_270);
}
if (!shift && !repeat && down) {
rotate_client_left(im->screen);
}
return;
case SDLK_RIGHT:
if (!repeat && down) {
if (shift) {
apply_orientation_transform(im->screen,
SC_ORIENTATION_FLIP_0);
} else {
apply_orientation_transform(im->screen,
SC_ORIENTATION_90);
}
if (!shift && !repeat && down) {
rotate_client_right(im->screen);
}
return;
case SDLK_c:

View File

@@ -39,8 +39,7 @@ const struct scrcpy_options scrcpy_options_default = {
.audio_bit_rate = 0,
.max_fps = 0,
.lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED,
.display_orientation = SC_ORIENTATION_0,
.record_orientation = SC_ORIENTATION_0,
.rotation = 0,
.window_x = SC_WINDOW_POSITION_UNDEFINED,
.window_y = SC_WINDOW_POSITION_UNDEFINED,
.window_width = 0,
@@ -90,39 +89,3 @@ const struct scrcpy_options scrcpy_options_default = {
.camera_high_speed = false,
.list = 0,
};
enum sc_orientation
sc_orientation_apply(enum sc_orientation src, enum sc_orientation transform) {
assert(!(src & ~7));
assert(!(transform & ~7));
unsigned transform_hflip = transform & 4;
unsigned transform_rotation = transform & 3;
unsigned src_hflip = src & 4;
unsigned src_rotation = src & 3;
unsigned src_swap = src & 1;
if (src_swap && transform_hflip) {
// If the src is rotated by 90 or 270 degrees, applying a flipped
// transformation requires an additional 180 degrees rotation to
// compensate for the inversion of the order of multiplication:
//
// hflip1 × rotate1 × hflip2 × rotate2
// `--------------' `--------------'
// src transform
//
// In the final result, we want all the hflips then all the rotations,
// so we must move hflip2 to the left:
//
// hflip1 × hflip2 × rotate1' × rotate2
//
// with rotate1' = | rotate1 if src is 0° or 180°
// | rotate1 + 180° if src is 90° or 270°
src_rotation += 2;
}
unsigned result_hflip = src_hflip ^ transform_hflip;
unsigned result_rotation = (transform_rotation + src_rotation) % 4;
enum sc_orientation result = result_hflip | result_rotation;
return result;
}

View File

@@ -3,7 +3,6 @@
#include "common.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
@@ -68,75 +67,14 @@ enum sc_camera_facing {
SC_CAMERA_FACING_EXTERNAL,
};
// ,----- hflip (applied before the rotation)
// | ,--- 180°
// | | ,- 90° clockwise
// | | |
enum sc_orientation { // v v v
SC_ORIENTATION_0, // 0 0 0
SC_ORIENTATION_90, // 0 0 1
SC_ORIENTATION_180, // 0 1 0
SC_ORIENTATION_270, // 0 1 1
SC_ORIENTATION_FLIP_0, // 1 0 0
SC_ORIENTATION_FLIP_90, // 1 0 1
SC_ORIENTATION_FLIP_180, // 1 1 0
SC_ORIENTATION_FLIP_270, // 1 1 1
};
static inline bool
sc_orientation_is_mirror(enum sc_orientation orientation) {
assert(!(orientation & ~7));
return orientation & 4;
}
// Does the orientation swap width and height?
static inline bool
sc_orientation_is_swap(enum sc_orientation orientation) {
assert(!(orientation & ~7));
return orientation & 1;
}
static inline enum sc_orientation
sc_orientation_get_rotation(enum sc_orientation orientation) {
assert(!(orientation & ~7));
return orientation & 3;
}
enum sc_orientation
sc_orientation_apply(enum sc_orientation src, enum sc_orientation transform);
static inline const char *
sc_orientation_get_name(enum sc_orientation orientation) {
switch (orientation) {
case SC_ORIENTATION_0:
return "0";
case SC_ORIENTATION_90:
return "90";
case SC_ORIENTATION_180:
return "180";
case SC_ORIENTATION_270:
return "270";
case SC_ORIENTATION_FLIP_0:
return "flip0";
case SC_ORIENTATION_FLIP_90:
return "flip90";
case SC_ORIENTATION_FLIP_180:
return "flip180";
case SC_ORIENTATION_FLIP_270:
return "flip270";
default:
return "(unknown)";
}
}
enum sc_lock_video_orientation {
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1,
// lock the current orientation when scrcpy starts
SC_LOCK_VIDEO_ORIENTATION_INITIAL = -2,
SC_LOCK_VIDEO_ORIENTATION_0 = 0,
SC_LOCK_VIDEO_ORIENTATION_90 = 3,
SC_LOCK_VIDEO_ORIENTATION_180 = 2,
SC_LOCK_VIDEO_ORIENTATION_270 = 1,
SC_LOCK_VIDEO_ORIENTATION_1,
SC_LOCK_VIDEO_ORIENTATION_2,
SC_LOCK_VIDEO_ORIENTATION_3,
};
enum sc_keyboard_input_mode {
@@ -219,8 +157,7 @@ struct scrcpy_options {
uint32_t audio_bit_rate;
uint16_t max_fps;
enum sc_lock_video_orientation lock_video_orientation;
enum sc_orientation display_orientation;
enum sc_orientation record_orientation;
uint8_t rotation;
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto"
uint16_t window_width;

View File

@@ -4,7 +4,6 @@
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/time.h>
#include <libavutil/display.h>
#include "util/log.h"
#include "util/str.h"
@@ -494,42 +493,6 @@ run_recorder(void *data) {
return 0;
}
static bool
sc_recorder_set_orientation(AVStream *stream, enum sc_orientation orientation) {
assert(!sc_orientation_is_mirror(orientation));
uint8_t *raw_data;
#ifdef SCRCPY_LAVC_HAS_CODECPAR_CODEC_SIDEDATA
AVPacketSideData *sd =
av_packet_side_data_new(&stream->codecpar->coded_side_data,
&stream->codecpar->nb_coded_side_data,
AV_PKT_DATA_DISPLAYMATRIX,
sizeof(int32_t) * 9, 0);
if (!sd) {
LOG_OOM();
return false;
}
raw_data = sd->data;
#else
raw_data = av_stream_new_side_data(stream, AV_PKT_DATA_DISPLAYMATRIX,
sizeof(int32_t) * 9);
if (!raw_data) {
LOG_OOM();
return false;
}
#endif
int32_t *matrix = (int32_t *) raw_data;
unsigned rotation = orientation;
unsigned angle = rotation * 90;
av_display_rotation_set(matrix, angle);
return true;
}
static bool
sc_recorder_video_packet_sink_open(struct sc_packet_sink *sink,
AVCodecContext *ctx) {
@@ -557,16 +520,6 @@ sc_recorder_video_packet_sink_open(struct sc_packet_sink *sink,
recorder->video_stream.index = stream->index;
if (recorder->orientation != SC_ORIENTATION_0) {
if (!sc_recorder_set_orientation(stream, recorder->orientation)) {
sc_mutex_unlock(&recorder->mutex);
return false;
}
LOGI("Record orientation set to %s",
sc_orientation_get_name(recorder->orientation));
}
recorder->video_init = true;
sc_cond_signal(&recorder->cond);
sc_mutex_unlock(&recorder->mutex);
@@ -648,7 +601,7 @@ sc_recorder_audio_packet_sink_open(struct sc_packet_sink *sink,
recorder->audio_stream.index = stream->index;
// A config packet is provided for all supported formats except raw audio
// A config packet is provided for all formats supported except raw audio
recorder->audio_expects_config_packet =
ctx->codec_id != AV_CODEC_ID_PCM_S16LE;
@@ -736,10 +689,7 @@ sc_recorder_stream_init(struct sc_recorder_stream *stream) {
bool
sc_recorder_init(struct sc_recorder *recorder, const char *filename,
enum sc_record_format format, bool video, bool audio,
enum sc_orientation orientation,
const struct sc_recorder_callbacks *cbs, void *cbs_userdata) {
assert(!sc_orientation_is_mirror(orientation));
recorder->filename = strdup(filename);
if (!recorder->filename) {
LOG_OOM();
@@ -760,8 +710,6 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename,
recorder->video = video;
recorder->audio = audio;
recorder->orientation = orientation;
sc_vecdeque_init(&recorder->video_queue);
sc_vecdeque_init(&recorder->audio_queue);
recorder->stopped = false;

View File

@@ -34,8 +34,6 @@ struct sc_recorder {
bool audio;
bool video;
enum sc_orientation orientation;
char *filename;
enum sc_record_format format;
AVFormatContext *ctx;
@@ -69,7 +67,6 @@ struct sc_recorder_callbacks {
bool
sc_recorder_init(struct sc_recorder *recorder, const char *filename,
enum sc_record_format format, bool video, bool audio,
enum sc_orientation orientation,
const struct sc_recorder_callbacks *cbs, void *cbs_userdata);
bool

View File

@@ -419,21 +419,12 @@ scrcpy(struct scrcpy_options *options) {
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
// still works.
// <https://github.com/Genymobile/scrcpy/issues/4418>
if (SDL_Init(SDL_INIT_VIDEO)) {
// If it fails, it is an error only if video playback is enabled
if (options->video_playback) {
LOGE("Could not initialize SDL video: %s", SDL_GetError());
goto end;
} else {
LOGW("Could not initialize SDL video: %s", SDL_GetError());
}
}
// Initialize the video subsystem even if --no-video or --no-video-playback
// is passed so that clipboard synchronization still works.
// <https://github.com/Genymobile/scrcpy/issues/4418>
if (SDL_Init(SDL_INIT_VIDEO)) {
LOGE("Could not initialize SDL video: %s", SDL_GetError());
goto end;
}
if (options->audio_playback) {
@@ -516,8 +507,7 @@ scrcpy(struct scrcpy_options *options) {
};
if (!sc_recorder_init(&s->recorder, options->record_filename,
options->record_format, options->video,
options->audio, options->record_orientation,
&recorder_cbs, NULL)) {
options->audio, &recorder_cbs, NULL)) {
goto end;
}
recorder_initialized = true;
@@ -701,7 +691,7 @@ aoa_hid_end:
.window_width = options->window_width,
.window_height = options->window_height,
.window_borderless = options->window_borderless,
.orientation = options->display_orientation,
.rotation = options->rotation,
.mipmaps = options->mipmaps,
.fullscreen = options->fullscreen,
.start_fps_counter = options->start_fps_counter,

View File

@@ -14,16 +14,16 @@
#define DOWNCAST(SINK) container_of(SINK, struct sc_screen, frame_sink)
static inline struct sc_size
get_oriented_size(struct sc_size size, enum sc_orientation orientation) {
struct sc_size oriented_size;
if (sc_orientation_is_swap(orientation)) {
oriented_size.width = size.height;
oriented_size.height = size.width;
get_rotated_size(struct sc_size size, int rotation) {
struct sc_size rotated_size;
if (rotation & 1) {
rotated_size.width = size.height;
rotated_size.height = size.width;
} else {
oriented_size.width = size.width;
oriented_size.height = size.height;
rotated_size.width = size.width;
rotated_size.height = size.height;
}
return oriented_size;
return rotated_size;
}
// get the window size in a struct sc_size
@@ -251,7 +251,7 @@ sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
}
enum sc_display_result res =
sc_display_render(&screen->display, &screen->rect, screen->orientation);
sc_display_render(&screen->display, &screen->rect, screen->rotation);
(void) res; // any error already logged
}
@@ -379,10 +379,9 @@ sc_screen_init(struct sc_screen *screen,
goto error_destroy_frame_buffer;
}
screen->orientation = params->orientation;
if (screen->orientation != SC_ORIENTATION_0) {
LOGI("Initial display orientation set to %s",
sc_orientation_get_name(screen->orientation));
screen->rotation = params->rotation;
if (screen->rotation) {
LOGI("Initial display rotation set to %u", screen->rotation);
}
uint32_t window_flags = SDL_WINDOW_HIDDEN
@@ -560,19 +559,19 @@ apply_pending_resize(struct sc_screen *screen) {
}
void
sc_screen_set_orientation(struct sc_screen *screen,
enum sc_orientation orientation) {
if (orientation == screen->orientation) {
sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation) {
assert(rotation < 4);
if (rotation == screen->rotation) {
return;
}
struct sc_size new_content_size =
get_oriented_size(screen->frame_size, orientation);
get_rotated_size(screen->frame_size, rotation);
set_content_size(screen, new_content_size);
screen->orientation = orientation;
LOGI("Display orientation set to %s", sc_orientation_get_name(orientation));
screen->rotation = rotation;
LOGI("Display rotation set to %u", rotation);
sc_screen_render(screen, true);
}
@@ -585,7 +584,7 @@ sc_screen_init_size(struct sc_screen *screen) {
// The requested size is passed via screen->frame_size
struct sc_size content_size =
get_oriented_size(screen->frame_size, screen->orientation);
get_rotated_size(screen->frame_size, screen->rotation);
screen->content_size = content_size;
enum sc_display_result res =
@@ -605,7 +604,7 @@ prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
screen->frame_size = new_frame_size;
struct sc_size new_content_size =
get_oriented_size(new_frame_size, screen->orientation);
get_rotated_size(new_frame_size, screen->rotation);
set_content_size(screen, new_content_size);
sc_screen_update_content_rect(screen);
@@ -844,7 +843,8 @@ 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) {
enum sc_orientation orientation = screen->orientation;
unsigned rotation = screen->rotation;
assert(rotation < 4);
int32_t w = screen->content_size.width;
int32_t h = screen->content_size.height;
@@ -855,43 +855,27 @@ sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
x = (int64_t) (x - screen->rect.x) * w / screen->rect.w;
y = (int64_t) (y - screen->rect.y) * h / screen->rect.h;
// rotate
struct sc_point result;
switch (orientation) {
case SC_ORIENTATION_0:
switch (rotation) {
case 0:
result.x = x;
result.y = y;
break;
case SC_ORIENTATION_90:
result.x = y;
result.y = w - x;
break;
case SC_ORIENTATION_180:
result.x = w - x;
result.y = h - y;
break;
case SC_ORIENTATION_270:
case 1:
result.x = h - y;
result.y = x;
break;
case SC_ORIENTATION_FLIP_0:
case 2:
result.x = w - x;
result.y = y;
break;
case SC_ORIENTATION_FLIP_90:
result.x = h - y;
result.y = w - x;
break;
case SC_ORIENTATION_FLIP_180:
result.x = x;
result.y = h - y;
break;
default:
assert(orientation == SC_ORIENTATION_FLIP_270);
assert(rotation == 3);
result.x = y;
result.y = x;
result.y = w - x;
break;
}
return result;
}

View File

@@ -14,7 +14,6 @@
#include "frame_buffer.h"
#include "input_manager.h"
#include "opengl.h"
#include "options.h"
#include "trait/key_processor.h"
#include "trait/frame_sink.h"
#include "trait/mouse_processor.h"
@@ -50,8 +49,8 @@ struct sc_screen {
// fullscreen (meaningful only when resize_pending is true)
struct sc_size windowed_content_size;
// client orientation
enum sc_orientation orientation;
// client rotation: 0, 1, 2 or 3 (x90 degrees counterclockwise)
unsigned rotation;
// rectangle of the content (excluding black borders)
struct SDL_Rect rect;
bool has_frame;
@@ -87,7 +86,7 @@ struct sc_screen_params {
bool window_borderless;
enum sc_orientation orientation;
uint8_t rotation;
bool mipmaps;
bool fullscreen;
@@ -130,10 +129,9 @@ sc_screen_resize_to_fit(struct sc_screen *screen);
void
sc_screen_resize_to_pixel_perfect(struct sc_screen *screen);
// set the display orientation
// set the display rotation (0, 1, 2 or 3, x90 degrees counterclockwise)
void
sc_screen_set_orientation(struct sc_screen *screen,
enum sc_orientation orientation);
sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation);
// react to SDL events
// If this function returns false, scrcpy must exit with an error.

View File

@@ -1,91 +0,0 @@
#include "common.h"
#include <assert.h>
#include "options.h"
static void test_transforms(void) {
#define O(X) SC_ORIENTATION_ ## X
#define ASSERT_TRANSFORM(SRC, TR, RES) \
assert(sc_orientation_apply(O(SRC), O(TR)) == O(RES));
ASSERT_TRANSFORM(0, 0, 0);
ASSERT_TRANSFORM(0, 90, 90);
ASSERT_TRANSFORM(0, 180, 180);
ASSERT_TRANSFORM(0, 270, 270);
ASSERT_TRANSFORM(0, FLIP_0, FLIP_0);
ASSERT_TRANSFORM(0, FLIP_90, FLIP_90);
ASSERT_TRANSFORM(0, FLIP_180, FLIP_180);
ASSERT_TRANSFORM(0, FLIP_270, FLIP_270);
ASSERT_TRANSFORM(90, 0, 90);
ASSERT_TRANSFORM(90, 90, 180);
ASSERT_TRANSFORM(90, 180, 270);
ASSERT_TRANSFORM(90, 270, 0);
ASSERT_TRANSFORM(90, FLIP_0, FLIP_270);
ASSERT_TRANSFORM(90, FLIP_90, FLIP_0);
ASSERT_TRANSFORM(90, FLIP_180, FLIP_90);
ASSERT_TRANSFORM(90, FLIP_270, FLIP_180);
ASSERT_TRANSFORM(180, 0, 180);
ASSERT_TRANSFORM(180, 90, 270);
ASSERT_TRANSFORM(180, 180, 0);
ASSERT_TRANSFORM(180, 270, 90);
ASSERT_TRANSFORM(180, FLIP_0, FLIP_180);
ASSERT_TRANSFORM(180, FLIP_90, FLIP_270);
ASSERT_TRANSFORM(180, FLIP_180, FLIP_0);
ASSERT_TRANSFORM(180, FLIP_270, FLIP_90);
ASSERT_TRANSFORM(270, 0, 270);
ASSERT_TRANSFORM(270, 90, 0);
ASSERT_TRANSFORM(270, 180, 90);
ASSERT_TRANSFORM(270, 270, 180);
ASSERT_TRANSFORM(270, FLIP_0, FLIP_90);
ASSERT_TRANSFORM(270, FLIP_90, FLIP_180);
ASSERT_TRANSFORM(270, FLIP_180, FLIP_270);
ASSERT_TRANSFORM(270, FLIP_270, FLIP_0);
ASSERT_TRANSFORM(FLIP_0, 0, FLIP_0);
ASSERT_TRANSFORM(FLIP_0, 90, FLIP_90);
ASSERT_TRANSFORM(FLIP_0, 180, FLIP_180);
ASSERT_TRANSFORM(FLIP_0, 270, FLIP_270);
ASSERT_TRANSFORM(FLIP_0, FLIP_0, 0);
ASSERT_TRANSFORM(FLIP_0, FLIP_90, 90);
ASSERT_TRANSFORM(FLIP_0, FLIP_180, 180);
ASSERT_TRANSFORM(FLIP_0, FLIP_270, 270);
ASSERT_TRANSFORM(FLIP_90, 0, FLIP_90);
ASSERT_TRANSFORM(FLIP_90, 90, FLIP_180);
ASSERT_TRANSFORM(FLIP_90, 180, FLIP_270);
ASSERT_TRANSFORM(FLIP_90, 270, FLIP_0);
ASSERT_TRANSFORM(FLIP_90, FLIP_0, 270);
ASSERT_TRANSFORM(FLIP_90, FLIP_90, 0);
ASSERT_TRANSFORM(FLIP_90, FLIP_180, 90);
ASSERT_TRANSFORM(FLIP_90, FLIP_270, 180);
ASSERT_TRANSFORM(FLIP_180, 0, FLIP_180);
ASSERT_TRANSFORM(FLIP_180, 90, FLIP_270);
ASSERT_TRANSFORM(FLIP_180, 180, FLIP_0);
ASSERT_TRANSFORM(FLIP_180, 270, FLIP_90);
ASSERT_TRANSFORM(FLIP_180, FLIP_0, 180);
ASSERT_TRANSFORM(FLIP_180, FLIP_90, 270);
ASSERT_TRANSFORM(FLIP_180, FLIP_180, 0);
ASSERT_TRANSFORM(FLIP_180, FLIP_270, 90);
ASSERT_TRANSFORM(FLIP_270, 0, FLIP_270);
ASSERT_TRANSFORM(FLIP_270, 90, FLIP_0);
ASSERT_TRANSFORM(FLIP_270, 180, FLIP_90);
ASSERT_TRANSFORM(FLIP_270, 270, FLIP_180);
ASSERT_TRANSFORM(FLIP_270, FLIP_0, 90);
ASSERT_TRANSFORM(FLIP_270, FLIP_90, 180);
ASSERT_TRANSFORM(FLIP_270, FLIP_180, 270);
ASSERT_TRANSFORM(FLIP_270, FLIP_270, 0);
}
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_transforms();
return 0;
}

View File

@@ -7,7 +7,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.1.3'
classpath 'com.android.tools.build:gradle:7.4.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@@ -23,3 +23,7 @@ allprojects {
options.compilerArgs << "-Xlint:deprecation"
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View File

@@ -2,7 +2,7 @@ apply plugin: 'checkstyle'
check.dependsOn 'checkstyle'
checkstyle {
toolVersion = '10.12.5'
toolVersion = '9.0.1'
}
task checkstyle(type: Checkstyle) {

View File

@@ -6,7 +6,7 @@ c = 'i686-w64-mingw32-gcc'
cpp = 'i686-w64-mingw32-g++'
ar = 'i686-w64-mingw32-ar'
strip = 'i686-w64-mingw32-strip'
pkg-config = 'i686-w64-mingw32-pkg-config'
pkgconfig = 'i686-w64-mingw32-pkg-config'
windres = 'i686-w64-mingw32-windres'
[host_machine]
@@ -14,3 +14,8 @@ system = 'windows'
cpu_family = 'x86'
cpu = 'i686'
endian = 'little'
[properties]
prebuilt_ffmpeg = 'ffmpeg-6.1-scrcpy-2/win32'
prebuilt_sdl2 = 'SDL2-2.28.4/i686-w64-mingw32'
prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-Win32'

View File

@@ -6,7 +6,7 @@ c = 'x86_64-w64-mingw32-gcc'
cpp = 'x86_64-w64-mingw32-g++'
ar = 'x86_64-w64-mingw32-ar'
strip = 'x86_64-w64-mingw32-strip'
pkg-config = 'x86_64-w64-mingw32-pkg-config'
pkgconfig = 'x86_64-w64-mingw32-pkg-config'
windres = 'x86_64-w64-mingw32-windres'
[host_machine]
@@ -14,3 +14,8 @@ system = 'windows'
cpu_family = 'x86'
cpu = 'x86_64'
endian = 'little'
[properties]
prebuilt_ffmpeg = 'ffmpeg-6.1-scrcpy-2/win64'
prebuilt_sdl2 = 'SDL2-2.28.4/x86_64-w64-mingw32'
prebuilt_libusb = 'libusb-1.0.26/libusb-MinGW-x64'

View File

@@ -58,7 +58,7 @@ sudo apt install gcc git pkg-config meson ninja-build libsdl2-dev \
libswresample-dev libusb-1.0-0-dev
# server build dependencies
sudo apt install openjdk-17-jdk
sudo apt install openjdk-11-jdk
```
On old versions (like Ubuntu 16.04), `meson` is too old. In that case, install
@@ -100,7 +100,7 @@ sudo apt install mingw-w64 mingw-w64-tools
You also need the JDK to build the server:
```bash
sudo apt install openjdk-17-jdk
sudo apt install openjdk-11-jdk
```
Then generate the releases:
@@ -168,13 +168,13 @@ brew install sdl2 ffmpeg libusb
brew install pkg-config meson
```
Additionally, if you want to build the server, install Java 17 from Caskroom, and
Additionally, if you want to build the server, install Java 8 from Caskroom, and
make it available from the `PATH`:
```bash
brew tap homebrew/cask-versions
brew install adoptopenjdk/openjdk/adoptopenjdk17
export JAVA_HOME="$(/usr/libexec/java_home --version 1.17)"
brew install adoptopenjdk/openjdk/adoptopenjdk11
export JAVA_HOME="$(/usr/libexec/java_home --version 1.11)"
export PATH="$JAVA_HOME/bin:$PATH"
```
@@ -233,10 +233,10 @@ install` must be run as root)._
#### Option 2: Use prebuilt server
- [`scrcpy-server-v2.3`][direct-scrcpy-server]
<sub>SHA-256: `8daed514d7796fca6987dc973e201bd15ba51d0f7258973dec92d9ded00dbd5f`</sub>
- [`scrcpy-server-v2.2`][direct-scrcpy-server]
<sub>SHA-256: `c85c4aa84305efb69115cd497a120ebdd10258993b4cf123a8245b3d99d49874`</sub>
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.3/scrcpy-server-v2.3
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.2/scrcpy-server-v2.2
Download the prebuilt server somewhere, and specify its path during the Meson
configuration:

View File

@@ -18,17 +18,6 @@ scrcpy --video-source=display --audio-source=mic # force display AND micropho
scrcpy --video-source=camera --audio-source=output # force camera AND device audio output
```
Audio can be disabled:
```bash
# audio not captured at all
scrcpy --video-source=camera --no-audio
scrcpy --video-source=camera --no-audio --record=file.mp4
# audio captured and recorded, but not played
scrcpy --video-source=camera --no-audio-playback --record=file.mp4
```
## List
@@ -112,16 +101,6 @@ scrcpy --video-source=camera --camera-size=1920x1080 -m3000 # error
```
## Rotation
To rotate the captured video, use the [video orientation](video.md#orientation)
option:
```
scrcpy --video-source=camera --camera-size=1920x1080 --orientation=90
```
## Frame rate
By default, camera is captured at Android's default frame rate (30 fps).

View File

@@ -50,12 +50,6 @@ scrcpy --record=file --record-format=mkv
```
## Rotation
The video can be recorded rotated. See [video
orientation](video.md#orientation).
## No playback
To disable playback while recording:

View File

@@ -26,8 +26,6 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
| Switch fullscreen mode | <kbd>MOD</kbd>+<kbd>f</kbd>
| Rotate display left | <kbd>MOD</kbd>+<kbd></kbd> _(left)_
| 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)_
| 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_

View File

@@ -21,13 +21,6 @@ This will create a new video device in `/dev/videoN`, where `N` is an integer
(more [options](https://github.com/umlaeute/v4l2loopback#options) are available
to create several devices or devices with specific IDs).
If you encounter problems detecting your device with Chrome/WebRTC, you can try
`exclusive_caps` mode:
```
sudo modprobe v4l2loopback exclusive_caps=1
```
To list the enabled devices:
```bash

View File

@@ -97,50 +97,39 @@ scrcpy --video-codec=h264 --video-encoder='OMX.qcom.video.encoder.avc'
```
## Orientation
## Rotation
The orientation may be applied at 3 different levels:
The rotation may be applied at 3 different levels:
- The [shortcut](shortcuts.md) <kbd>MOD</kbd>+<kbd>r</kbd> requests the
device to switch between portrait and landscape (the current running app may
refuse, if it does not support the requested orientation).
- `--lock-video-orientation` changes the mirroring orientation (the orientation
of the video sent from the device to the computer). This affects the
recording.
- `--orientation` is applied on the client side, and affects display and
recording. For the display, it can be changed dynamically using
[shortcuts](shortcuts.md).
- `--rotation` rotates only the window content. This only affects the display,
not the recording. It may be changed dynamically at any time using the
[shortcuts](shortcuts.md) <kbd>MOD</kbd>+<kbd></kbd> and
<kbd>MOD</kbd>+<kbd></kbd>.
To lock the mirroring orientation (on the capture side):
To lock the mirroring orientation:
```bash
scrcpy --lock-video-orientation # initial (current) orientation
scrcpy --lock-video-orientation=0 # natural orientation
scrcpy --lock-video-orientation=90 # 90° clockwise
scrcpy --lock-video-orientation=180 # 180°
scrcpy --lock-video-orientation=270 # 270° clockwise
scrcpy --lock-video-orientation # initial (current) orientation
scrcpy --lock-video-orientation=0 # natural orientation
scrcpy --lock-video-orientation=1 # 90° counterclockwise
scrcpy --lock-video-orientation=2 # 180°
scrcpy --lock-video-orientation=3 # 90° clockwise
```
To orient the video (on the rendering side):
To set an initial window rotation:
```bash
scrcpy --orientation=0
scrcpy --orientation=90 # 90° clockwise
scrcpy --orientation=180 # 180°
scrcpy --orientation=270 # 270° clockwise
scrcpy --orientation=flip0 # hflip
scrcpy --orientation=flip90 # hflip + 90° clockwise
scrcpy --orientation=flip180 # vflip (hflip + 180°)
scrcpy --orientation=flip270 # hflip + 270° clockwise
scrcpy --rotation=0 # no rotation
scrcpy --rotation=1 # 90 degrees counterclockwise
scrcpy --rotation=2 # 180 degrees
scrcpy --rotation=3 # 90 degrees clockwise
```
The orientation can be set separately for display and record if necessary, via
`--display-orientation` and `--record-orientation`.
The rotation is applied to a recorded file by writing a display transformation
to the MP4 or MKV target file. Flipping is not supported, so only the 4 first
values are allowed when recording.
## Crop
The device screen may be cropped to mirror only part of the screen.

View File

@@ -4,14 +4,14 @@
Download the [latest release]:
- [`scrcpy-win64-v2.3.zip`][direct-win64] (64-bit)
<sub>SHA-256: `a2fdd2733bd337261bb493e77d990078a23e7a40149dd0c0dc45725c929a715f`</sub>
- [`scrcpy-win32-v2.3.zip`][direct-win32] (32-bit)
<sub>SHA-256: `dfdbb69a872d717aed5bcfe352e571564c357fdb7a9c172d69f450fdf5154a0a`</sub>
- [`scrcpy-win64-v2.2.zip`][direct-win64] (64-bit)
<sub>SHA-256: `9f9da88ac4c8319dcb9bf852f2d9bba942bac663413383419cddf64eaa5685bd`</sub>
- [`scrcpy-win32-v2.2.zip`][direct-win32] (32-bit)
<sub>SHA-256: `cb84269fc847b8b880e320879492a1ae6c017b42175f03e199530f7a53be9d74`</sub>
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.3/scrcpy-win64-v2.3.zip
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.3/scrcpy-win32-v2.3.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.2/scrcpy-win64-v2.2.zip
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.2/scrcpy-win32-v2.2.zip
and extract it.

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

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

View File

@@ -1,5 +1,5 @@
project('scrcpy', 'c',
version: '2.3.1',
version: 'v2.2',
meson_version: '>= 0.48',
default_options: [
'c_std=c11',

View File

@@ -69,62 +69,58 @@ prepare-deps:
@app/prebuilt-deps/prepare-libusb.sh
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="$(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
[ -d "$(WIN32_BUILD_DIR)" ] || ( mkdir "$(WIN32_BUILD_DIR)" && \
meson setup "$(WIN32_BUILD_DIR)" \
--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
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="$(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 \
-Dportable=true
[ -d "$(WIN64_BUILD_DIR)" ] || ( mkdir "$(WIN64_BUILD_DIR)" && \
meson setup "$(WIN64_BUILD_DIR)" \
--cross-file cross_win64.txt \
--buildtype release --strip -Db_lto=true \
-Dcompile_server=false \
-Dportable=true )
ninja -C "$(WIN64_BUILD_DIR)"
dist-win32: build-server build-win32
mkdir -p "$(DIST)/$(WIN32_TARGET_DIR)"
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN32_TARGET_DIR)/"
cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/data/scrcpy-console.bat "$(DIST)/$(WIN32_TARGET_DIR)/"
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/data/scrcpy-console.bat "$(DIST)/$(WIN32_TARGET_DIR)"
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/prebuilt-deps/data/ffmpeg-6.1-scrcpy-2/win32/bin/avutil-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-2/win32/bin/avcodec-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-2/win32/bin/avformat-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-2/win32/bin/swresample-4.dll "$(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)/"
cp app/prebuilt-deps/data/SDL2-2.28.4/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-Win32/bin/msys-usb-1.0.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
dist-win64: build-server build-win64
mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)"
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN64_TARGET_DIR)/"
cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/data/scrcpy-console.bat "$(DIST)/$(WIN64_TARGET_DIR)/"
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/data/scrcpy-console.bat "$(DIST)/$(WIN64_TARGET_DIR)"
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/prebuilt-deps/data/ffmpeg-6.1-scrcpy-2/win64/bin/avutil-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-2/win64/bin/avcodec-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-2/win64/bin/avformat-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-2/win64/bin/swresample-4.dll "$(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)/"
cp app/prebuilt-deps/data/SDL2-2.28.4/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-x64/bin/msys-usb-1.0.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
zip-win32: dist-win32
cd "$(DIST)"; \

View File

@@ -2,13 +2,13 @@ apply plugin: 'com.android.application'
android {
namespace 'com.genymobile.scrcpy'
compileSdk 34
compileSdkVersion 33
defaultConfig {
applicationId "com.genymobile.scrcpy"
minSdkVersion 21
targetSdkVersion 34
versionCode 20301
versionName "2.3.1"
targetSdkVersion 33
versionCode 200
versionName "v2.2"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
@@ -17,10 +17,6 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
buildFeatures {
buildConfig true
aidl true
}
}
dependencies {

View File

@@ -12,10 +12,10 @@
set -e
SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=2.3.1
SCRCPY_VERSION_NAME=v2.2
PLATFORM=${ANDROID_PLATFORM:-34}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-34.0.0}
PLATFORM=${ANDROID_PLATFORM:-33}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-33.0.0}
BUILD_TOOLS_DIR="$ANDROID_HOME/build-tools/$BUILD_TOOLS"
BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})"

View File

@@ -11,8 +11,6 @@ public interface AsyncProcessor {
}
void start(TerminationListener listener);
void stop();
void join() throws InterruptedException;
}

View File

@@ -295,7 +295,7 @@ public final class AudioEncoder implements AsyncProcessor {
}
}
private final class EncoderCallback extends MediaCodec.Callback {
private class EncoderCallback extends MediaCodec.Callback {
@TargetApi(Build.VERSION_CODES.N)
@Override
public void onInputBufferAvailable(MediaCodec codec, int index) {

View File

@@ -289,17 +289,18 @@ public class CameraCapture extends SurfaceCapture {
List<OutputConfiguration> outputs = Arrays.asList(outputConfig);
int sessionType = highSpeed ? SessionConfiguration.SESSION_HIGH_SPEED : SessionConfiguration.SESSION_REGULAR;
SessionConfiguration sessionConfig = new SessionConfiguration(sessionType, outputs, cameraExecutor, new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(CameraCaptureSession session) {
future.complete(session);
}
SessionConfiguration sessionConfig = new SessionConfiguration(sessionType, outputs, cameraExecutor,
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(CameraCaptureSession session) {
future.complete(session);
}
@Override
public void onConfigureFailed(CameraCaptureSession session) {
future.completeExceptionally(new CameraAccessException(CameraAccessException.CAMERA_ERROR));
}
});
@Override
public void onConfigureFailed(CameraCaptureSession session) {
future.completeExceptionally(new CameraAccessException(CameraAccessException.CAMERA_ERROR));
}
});
camera.createCaptureSession(sessionConfig);

View File

@@ -14,6 +14,8 @@ import java.io.IOException;
*/
public final class CleanUp {
public static final String SERVER_PATH = "/data/local/tmp/scrcpy-server.jar";
// A simple struct to be passed from the main process to the cleanup process
public static class Config implements Parcelable {
@@ -133,13 +135,13 @@ public final class CleanUp {
String[] cmd = {"app_process", "/", CleanUp.class.getName(), config.toBase64()};
ProcessBuilder builder = new ProcessBuilder(cmd);
builder.environment().put("CLASSPATH", Server.SERVER_PATH);
builder.environment().put("CLASSPATH", SERVER_PATH);
builder.start();
}
public static void unlinkSelf() {
try {
new File(Server.SERVER_PATH).delete();
new File(SERVER_PATH).delete();
} catch (Exception e) {
Ln.e("Could not unlink server", e);
}

View File

@@ -318,8 +318,9 @@ public class Controller implements AsyncProcessor {
}
}
MotionEvent event = MotionEvent.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f,
DEFAULT_DEVICE_ID, 0, source, 0);
MotionEvent event = MotionEvent
.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source,
0);
return device.injectEvent(event, Device.INJECT_MODE_ASYNC);
}
@@ -340,8 +341,9 @@ public class Controller implements AsyncProcessor {
coords.setAxisValue(MotionEvent.AXIS_HSCROLL, hScroll);
coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll);
MotionEvent event = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, buttons, 1f, 1f,
DEFAULT_DEVICE_ID, 0, InputDevice.SOURCE_MOUSE, 0);
MotionEvent event = MotionEvent
.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0,
InputDevice.SOURCE_MOUSE, 0);
return device.injectEvent(event, Device.INJECT_MODE_ASYNC);
}

View File

@@ -1,7 +1,6 @@
package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.ClipboardManager;
import com.genymobile.scrcpy.wrappers.DisplayControl;
import com.genymobile.scrcpy.wrappers.InputManager;
import com.genymobile.scrcpy.wrappers.ServiceManager;
import com.genymobile.scrcpy.wrappers.SurfaceControl;
@@ -12,8 +11,8 @@ import android.graphics.Rect;
import android.os.Build;
import android.os.IBinder;
import android.os.SystemClock;
import android.view.IDisplayFoldListener;
import android.view.IRotationWatcher;
import android.view.IDisplayFoldListener;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.KeyCharacterMap;
@@ -316,12 +315,8 @@ public final class Device {
*/
public static boolean setScreenPowerMode(int mode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// On Android 14, these internal methods have been moved to DisplayControl
boolean useDisplayControl =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && !SurfaceControl.hasPhysicalDisplayIdsMethod();
// Change the power mode for all physical displays
long[] physicalDisplayIds = useDisplayControl ? DisplayControl.getPhysicalDisplayIds() : SurfaceControl.getPhysicalDisplayIds();
long[] physicalDisplayIds = SurfaceControl.getPhysicalDisplayIds();
if (physicalDisplayIds == null) {
Ln.e("Could not get physical display ids");
return false;
@@ -329,8 +324,7 @@ public final class Device {
boolean allOk = true;
for (long physicalDisplayId : physicalDisplayIds) {
IBinder binder = useDisplayControl ? DisplayControl.getPhysicalDisplayToken(
physicalDisplayId) : SurfaceControl.getPhysicalDisplayToken(physicalDisplayId);
IBinder binder = SurfaceControl.getPhysicalDisplayToken(physicalDisplayId);
allOk &= SurfaceControl.setDisplayPowerMode(binder, mode);
}
return allOk;

View File

@@ -51,7 +51,6 @@ public final class DeviceMessageSender {
}
}
}
public void start() {
thread = new Thread(() -> {
try {

View File

@@ -18,6 +18,7 @@ public class ScreenCapture extends SurfaceCapture implements Device.RotationList
@Override
public void init() {
display = createDisplay();
device.setRotationListener(this);
device.setFoldListener(this);
}
@@ -31,11 +32,6 @@ public class ScreenCapture extends SurfaceCapture implements Device.RotationList
Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect();
int videoRotation = screenInfo.getVideoRotation();
int layerStack = device.getLayerStack();
if (display != null) {
SurfaceControl.destroyDisplay(display);
}
display = createDisplay();
setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
}
@@ -43,9 +39,7 @@ public class ScreenCapture extends SurfaceCapture implements Device.RotationList
public void release() {
device.setRotationListener(null);
device.setFoldListener(null);
if (display != null) {
SurfaceControl.destroyDisplay(display);
}
SurfaceControl.destroyDisplay(display);
}
@Override

View File

@@ -3,21 +3,12 @@ package com.genymobile.scrcpy;
import android.os.BatteryManager;
import android.os.Build;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public final class Server {
public static final String SERVER_PATH;
static {
String[] classPaths = System.getProperty("java.class.path").split(File.pathSeparator);
// By convention, scrcpy is always executed with the absolute path of scrcpy-server.jar as the first item in the classpath
SERVER_PATH = classPaths[0];
}
private static class Completion {
private int running;
private boolean fatalError;
@@ -101,6 +92,8 @@ public final class Server {
throw new ConfigurationException("Camera mirroring is not supported");
}
final Device device = new Device(options);
Thread initThread = startInitThread(options);
int scid = options.getScid();
@@ -109,9 +102,7 @@ public final class Server {
boolean video = options.getVideo();
boolean audio = options.getAudio();
boolean sendDummyByte = options.getSendDummyByte();
boolean camera = video && options.getVideoSource() == VideoSource.CAMERA;
final Device device = camera ? null : new Device(options);
boolean camera = options.getVideoSource() == VideoSource.CAMERA;
Workarounds.apply(audio, camera);

View File

@@ -75,7 +75,7 @@ public final class Settings {
String oldValue = getValue(table, key);
if (!value.equals(oldValue)) {
putValue(table, key, value);
putValue(table, key, value);
}
return oldValue;
}

View File

@@ -19,7 +19,6 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@SuppressLint("PrivateApi,BlockedPrivateApi,SoonBlockedPrivateApi,DiscouragedPrivateApi")
public final class Workarounds {
private static final Class<?> ACTIVITY_THREAD_CLASS;
@@ -49,7 +48,6 @@ public final class Workarounds {
}
public static void apply(boolean audio, boolean camera) {
boolean mustFillConfigurationController = false;
boolean mustFillAppInfo = false;
boolean mustFillAppContext = false;
@@ -86,23 +84,11 @@ public final class Workarounds {
mustFillAppContext = true;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// On some Samsung devices, DisplayManagerGlobal.getDisplayInfoLocked() calls ActivityThread.currentActivityThread().getConfiguration(),
// which requires a non-null ConfigurationController.
// ConfigurationController was introduced in Android 12, so do not attempt to set it on lower versions.
// <https://github.com/Genymobile/scrcpy/issues/4467>
mustFillConfigurationController = true;
}
if (mustFillConfigurationController) {
// Must be call before fillAppContext() because it is necessary to get a valid system context
fillConfigurationController();
}
if (mustFillAppInfo) {
fillAppInfo();
Workarounds.fillAppInfo();
}
if (mustFillAppContext) {
fillAppContext();
Workarounds.fillAppContext();
}
}
@@ -119,6 +105,7 @@ public final class Workarounds {
Looper.prepareMainLooper();
}
@SuppressLint("PrivateApi,DiscouragedPrivateApi")
private static void fillAppInfo() {
try {
// ActivityThread.AppBindData appBindData = new ActivityThread.AppBindData();
@@ -145,6 +132,7 @@ public final class Workarounds {
}
}
@SuppressLint("PrivateApi,DiscouragedPrivateApi")
private static void fillAppContext() {
try {
Application app = new Application();
@@ -162,22 +150,6 @@ public final class Workarounds {
}
}
private static void fillConfigurationController() {
try {
Class<?> configurationControllerClass = Class.forName("android.app.ConfigurationController");
Class<?> activityThreadInternalClass = Class.forName("android.app.ActivityThreadInternal");
Constructor<?> configurationControllerConstructor = configurationControllerClass.getDeclaredConstructor(activityThreadInternalClass);
configurationControllerConstructor.setAccessible(true);
Object configurationController = configurationControllerConstructor.newInstance(ACTIVITY_THREAD);
Field configurationControllerField = ACTIVITY_THREAD_CLASS.getDeclaredField("mConfigurationController");
configurationControllerField.setAccessible(true);
configurationControllerField.set(ACTIVITY_THREAD, configurationController);
} catch (Throwable throwable) {
Ln.d("Could not fill configuration: " + throwable.getMessage());
}
}
static Context getSystemContext() {
try {
Method getSystemContextMethod = ACTIVITY_THREAD_CLASS.getDeclaredMethod("getSystemContext");
@@ -190,7 +162,7 @@ public final class Workarounds {
}
@TargetApi(Build.VERSION_CODES.R)
@SuppressLint("WrongConstant,MissingPermission")
@SuppressLint("WrongConstant,MissingPermission,BlockedPrivateApi,SoonBlockedPrivateApi,DiscouragedPrivateApi")
public static AudioRecord createAudioRecord(int source, int sampleRate, int channelConfig, int channels, int channelMask, int encoding) {
// Vivo (and maybe some other third-party ROMs) modified `AudioRecord`'s constructor, requiring `Context`s from real App environment.
//

View File

@@ -138,8 +138,8 @@ public final class ClipboardManager {
}
}
private static void addPrimaryClipChangedListener(Method method, int methodVersion, IInterface manager, IOnPrimaryClipChangedListener listener)
throws InvocationTargetException, IllegalAccessException {
private static void addPrimaryClipChangedListener(Method method, int methodVersion, IInterface manager,
IOnPrimaryClipChangedListener listener) throws InvocationTargetException, IllegalAccessException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
method.invoke(manager, listener, FakeContext.PACKAGE_NAME);
return;

View File

@@ -1,80 +0,0 @@
package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.Ln;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.os.Build;
import android.os.IBinder;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@SuppressLint({"PrivateApi", "SoonBlockedPrivateApi", "BlockedPrivateApi"})
@TargetApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public final class DisplayControl {
private static final Class<?> CLASS;
static {
Class<?> displayControlClass = null;
try {
Class<?> classLoaderFactoryClass = Class.forName("com.android.internal.os.ClassLoaderFactory");
Method createClassLoaderMethod = classLoaderFactoryClass.getDeclaredMethod("createClassLoader", String.class, String.class, String.class,
ClassLoader.class, int.class, boolean.class, String.class);
ClassLoader classLoader = (ClassLoader) createClassLoaderMethod.invoke(null, "/system/framework/services.jar", null, null,
ClassLoader.getSystemClassLoader(), 0, true, null);
displayControlClass = classLoader.loadClass("com.android.server.display.DisplayControl");
Method loadMethod = Runtime.class.getDeclaredMethod("loadLibrary0", Class.class, String.class);
loadMethod.setAccessible(true);
loadMethod.invoke(Runtime.getRuntime(), displayControlClass, "android_servers");
} catch (Throwable e) {
Ln.e("Could not initialize DisplayControl", e);
// Do not throw an exception here, the methods will fail when they are called
}
CLASS = displayControlClass;
}
private static Method getPhysicalDisplayTokenMethod;
private static Method getPhysicalDisplayIdsMethod;
private DisplayControl() {
// only static methods
}
private static Method getGetPhysicalDisplayTokenMethod() throws NoSuchMethodException {
if (getPhysicalDisplayTokenMethod == null) {
getPhysicalDisplayTokenMethod = CLASS.getMethod("getPhysicalDisplayToken", long.class);
}
return getPhysicalDisplayTokenMethod;
}
public static IBinder getPhysicalDisplayToken(long physicalDisplayId) {
try {
Method method = getGetPhysicalDisplayTokenMethod();
return (IBinder) method.invoke(null, physicalDisplayId);
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
Ln.e("Could not invoke method", e);
return null;
}
}
private static Method getGetPhysicalDisplayIdsMethod() throws NoSuchMethodException {
if (getPhysicalDisplayIdsMethod == null) {
getPhysicalDisplayIdsMethod = CLASS.getMethod("getPhysicalDisplayIds");
}
return getPhysicalDisplayIdsMethod;
}
public static long[] getPhysicalDisplayIds() {
try {
Method method = getGetPhysicalDisplayIdsMethod();
return (long[]) method.invoke(null);
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
Ln.e("Could not invoke method", e);
return null;
}
}
}

View File

@@ -20,7 +20,7 @@ public final class PowerManager {
private Method getIsScreenOnMethod() throws NoSuchMethodException {
if (isScreenOnMethod == null) {
@SuppressLint("ObsoleteSdkInt") // we may lower minSdkVersion in the future
String methodName = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH ? "isInteractive" : "isScreenOn";
String methodName = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH ? "isInteractive" : "isScreenOn";
isScreenOnMethod = manager.getClass().getMethod(methodName);
}
return isScreenOnMethod;

View File

@@ -16,7 +16,6 @@ import java.lang.reflect.Method;
public final class ServiceManager {
private static final Method GET_SERVICE_METHOD;
static {
try {
GET_SERVICE_METHOD = Class.forName("android.os.ServiceManager").getDeclaredMethod("getService", String.class);

View File

@@ -139,15 +139,6 @@ public final class SurfaceControl {
return getPhysicalDisplayIdsMethod;
}
public static boolean hasPhysicalDisplayIdsMethod() {
try {
getGetPhysicalDisplayIdsMethod();
return true;
} catch (NoSuchMethodException e) {
return false;
}
}
public static long[] getPhysicalDisplayIds() {
try {
Method method = getGetPhysicalDisplayIdsMethod();

View File

@@ -4,8 +4,8 @@ import com.genymobile.scrcpy.Ln;
import android.annotation.TargetApi;
import android.os.IInterface;
import android.view.IDisplayFoldListener;
import android.view.IRotationWatcher;
import android.view.IDisplayFoldListener;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;