Compare commits

..

10 Commits

Author SHA1 Message Date
Romain Vimont
67fa766f09 Fix turn screen off on Android 14
On Android 14, execute a separate process with a different classpath and
LD_PRELOAD to execute the methods required to turn the device screen
off.

Fixes #3927 <https://github.com/Genymobile/scrcpy/issues/3927>
Refs #3927 comment <https://github.com/Genymobile/scrcpy/issues/3927#issuecomment-1790031953>

Co-authored-by: Simon Chan <1330321+yume-chan@users.noreply.github.com>
2023-11-21 17:59:45 +01:00
Romain Vimont
890ba529c3 Keep server file on the device until clean up
The server was unlinked (removed) just after it started.

In order to execute a new process using the server jarfile at any time
(typically to set the display power mode from another process), keep the
file until the server closes.
2023-11-21 17:01:17 +01:00
Romain Vimont
798727aa58 Use a different server path for each session
When scrcpy is run, a server is pushed to
/data/local/tmp/scrcpy-server.jar.

Running simultaneous scrcpy instances on the same device was not a
problem, because the file was unlinked (removed) almost immediately once
it started, avoiding any conflicts.

In order to support executing new process using the scrcpy-server.jar at
any time (to change the display power mode from a separate process), the
server file must not be unlinked, so using different names are necessary
to avoid conflicts.

Reuse the scid mechanism already used for generating device socket
names.

Refs 4315be1648
Refs 439a1fd4ed
2023-11-21 17:01:06 +01:00
sam80180
e76ff25e31 Do not hardcode server path on the device
The path can be retrieved from the classpath.

PR #4416 <https://github.com/Genymobile/scrcpy/pull/4416>

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2023-11-21 16:59:37 +01:00
Romain Vimont
ec80be1eda Fix meson deprecated 'pkgconfig' to 'pkg-config'
When running ./release.sh:

> DEPRECATION: "pkgconfig" entry is deprecated and should be replaced by
> "pkg-config"
2023-11-21 16:59:37 +01:00
Romain Vimont
cdd78273dc Update documentation about video orientation 2023-11-21 00:01:28 +01:00
Romain Vimont
c546293f35 Add --orientation
Add a shortcut to set both the display and record orientations.
2023-11-21 00:01:28 +01:00
Romain Vimont
70baf3c384 Add --record-orientation
Add an option to store the orientation to apply in a recorded file.

Only rotations are supported (not flip), and most players correctly
handle it only for MP4 files (not MKV files).
2023-11-21 00:01:28 +01:00
Romain Vimont
dac0d54c5c Pass --lock-video-orientation argument in degrees
For consistency with the new --display-orientation option, express the
--lock-video-orientation in degrees clockwise:

 * --lock-video-orientation=0 -> --lock-video-orientation=0
 * --lock-video-orientation=3 -> --lock-video-orientation=90
 * --lock-video-orientation=2 -> --lock-video-orientation=180
 * --lock-video-orientation=1 -> --lock-video-orientation=270
2023-11-21 00:01:28 +01:00
Romain Vimont
878bfb1d8b Add --display-orientation
Deprecate the option --rotation and introduce a new option
--display-orientation with the 8 possible orientations (0, 90, 180, 270,
flip0, flip90, flip180 and flip270).

New shortcuts MOD+Shift+(arrow) dynamically change the display
(horizontal or vertical) flip.

Fixes #1380 <https://github.com/Genymobile/scrcpy/issues/1380>
Fixes #3819 <https://github.com/Genymobile/scrcpy/issues/3819>
2023-11-21 00:01:13 +01:00
38 changed files with 376 additions and 277 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" /> <img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />

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 # 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 # startup file, like .bashrc or .zshrc… Run an interactive shell to get
# environment correctly initialized. # 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 Icon=scrcpy
Terminal=true Terminal=true
Type=Application 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 # 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 # startup file, like .bashrc or .zshrc… Run an interactive shell to get
# environment correctly initialized. # environment correctly initialized.
Exec=/bin/sh -c "\\$SHELL -i -c scrcpy" Exec=/bin/sh -c "\"\\$SHELL\" -i -c scrcpy"
Icon=scrcpy Icon=scrcpy
Terminal=false Terminal=false
Type=Application Type=Application

View File

@@ -98,24 +98,77 @@ endif
cc = meson.get_compiler('c') cc = meson.get_compiler('c')
dependencies = [ crossbuild_windows = meson.is_cross_build() and host_machine.system() == 'windows'
dependency('libavformat', version: '>= 57.33'),
dependency('libavcodec', version: '>= 57.37'),
dependency('libavutil'),
dependency('libswresample'),
dependency('sdl2', version: '>= 2.0.5'),
]
if v4l2_support if not crossbuild_windows
dependencies += dependency('libavdevice')
endif # 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 endif
if host_machine.system() == 'windows' if host_machine.system() == 'windows'
dependencies += cc.find_library('mingw32')
dependencies += cc.find_library('ws2_32') dependencies += cc.find_library('ws2_32')
endif endif

View File

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

View File

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

View File

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

View File

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

View File

@@ -2138,7 +2138,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
opts->display_orientation = SC_ORIENTATION_180; opts->display_orientation = SC_ORIENTATION_180;
break; break;
case 3: case 3:
// rotation 3 was 270° counterclockwise, but orientation // rotation 1 was 270° counterclockwise, but orientation
// is expressed clockwise // is expressed clockwise
opts->display_orientation = SC_ORIENTATION_90; opts->display_orientation = SC_ORIENTATION_90;
break; break;
@@ -2154,7 +2154,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
return false; return false;
} }
break; break;
case OPT_ORIENTATION: { case OPT_ORIENTATION:
enum sc_orientation orientation; enum sc_orientation orientation;
if (!parse_orientation(optarg, &orientation)) { if (!parse_orientation(optarg, &orientation)) {
return false; return false;
@@ -2162,7 +2162,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
opts->display_orientation = orientation; opts->display_orientation = orientation;
opts->record_orientation = orientation; opts->record_orientation = orientation;
break; break;
}
case OPT_RENDER_DRIVER: case OPT_RENDER_DRIVER:
opts->render_driver = optarg; opts->render_driver = optarg;
break; break;
@@ -2532,8 +2531,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
if (opts->record_orientation != SC_ORIENTATION_0) { if (opts->record_orientation != SC_ORIENTATION_0) {
if (sc_orientation_is_mirror(opts->record_orientation)) { if (sc_orientation_is_mirror(opts->record_orientation)) {
LOGE("Record orientation only supports rotation, not " LOGE("Record orientation only supports rotation, not "
"flipping: %s", "flipping: %s", optarg);
sc_orientation_get_name(opts->record_orientation));
return false; return false;
} }
} }

View File

@@ -113,10 +113,10 @@ sc_orientation_apply(enum sc_orientation src, enum sc_orientation transform) {
// In the final result, we want all the hflips then all the rotations, // In the final result, we want all the hflips then all the rotations,
// so we must move hflip2 to the left: // so we must move hflip2 to the left:
// //
// hflip1 × hflip2 × rotate1' × rotate2 // hflip1 × hflip2 × f(rotate1) × rotate2
// //
// with rotate1' = | rotate1 if src is 0° or 180° // with f(rotate1) = | rotate1 if src is 0 or 180
// | rotate1 + 180° if src is 90° or 270° // | rotate1 + 180 if src is 90 or 270
src_rotation += 2; src_rotation += 2;
} }

View File

@@ -419,21 +419,12 @@ scrcpy(struct scrcpy_options *options) {
sdl_set_hints(options->render_driver); sdl_set_hints(options->render_driver);
} }
if (options->video_playback || // Initialize the video subsystem even if --no-video or --no-video-playback
(options->control && options->clipboard_autosync)) { // is passed so that clipboard synchronization still works.
// Initialize the video subsystem even if --no-video or // <https://github.com/Genymobile/scrcpy/issues/4418>
// --no-video-playback is passed so that clipboard synchronization if (SDL_Init(SDL_INIT_VIDEO)) {
// still works. LOGE("Could not initialize SDL video: %s", SDL_GetError());
// <https://github.com/Genymobile/scrcpy/issues/4418> goto end;
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());
}
}
} }
if (options->audio_playback) { if (options->audio_playback) {

View File

@@ -18,7 +18,6 @@
#define SC_SERVER_FILENAME "scrcpy-server" #define SC_SERVER_FILENAME "scrcpy-server"
#define SC_SERVER_PATH_DEFAULT PREFIX "/share/scrcpy/" SC_SERVER_FILENAME #define SC_SERVER_PATH_DEFAULT PREFIX "/share/scrcpy/" SC_SERVER_FILENAME
#define SC_DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar"
#define SC_ADB_PORT_DEFAULT 5555 #define SC_ADB_PORT_DEFAULT 5555
#define SC_SOCKET_NAME_PREFIX "scrcpy_" #define SC_SOCKET_NAME_PREFIX "scrcpy_"
@@ -117,7 +116,7 @@ error:
} }
static bool static bool
push_server(struct sc_intr *intr, const char *serial) { push_server(struct sc_intr *intr, uint32_t scid, const char *serial) {
char *server_path = get_server_path(); char *server_path = get_server_path();
if (!server_path) { if (!server_path) {
return false; return false;
@@ -127,7 +126,16 @@ push_server(struct sc_intr *intr, const char *serial) {
free(server_path); free(server_path);
return false; return false;
} }
bool ok = sc_adb_push(intr, serial, server_path, SC_DEVICE_SERVER_PATH, 0);
char *device_server_path;
if (asprintf(&device_server_path, "/data/local/tmp/scrcpy-server-%08x.jar",
scid) == -1) {
LOG_OOM();
free(server_path);
return false;
}
bool ok = sc_adb_push(intr, serial, server_path, device_server_path, 0);
free(device_server_path);
free(server_path); free(server_path);
return ok; return ok;
} }
@@ -209,13 +217,20 @@ execute_server(struct sc_server *server,
const char *serial = server->serial; const char *serial = server->serial;
assert(serial); assert(serial);
char *classpath;
if (asprintf(&classpath, "CLASSPATH=/data/local/tmp/scrcpy-server-%08x.jar",
params->scid) == -1) {
LOG_OOM();
return SC_PROCESS_NONE;
}
const char *cmd[128]; const char *cmd[128];
unsigned count = 0; unsigned count = 0;
cmd[count++] = sc_adb_get_executable(); cmd[count++] = sc_adb_get_executable();
cmd[count++] = "-s"; cmd[count++] = "-s";
cmd[count++] = serial; cmd[count++] = serial;
cmd[count++] = "shell"; cmd[count++] = "shell";
cmd[count++] = "CLASSPATH=" SC_DEVICE_SERVER_PATH; cmd[count++] = classpath;
cmd[count++] = "app_process"; cmd[count++] = "app_process";
#ifdef SERVER_DEBUGGER #ifdef SERVER_DEBUGGER
@@ -388,6 +403,7 @@ end:
for (unsigned i = dyn_idx; i < count; ++i) { for (unsigned i = dyn_idx; i < count; ++i) {
free((char *) cmd[i]); free((char *) cmd[i]);
} }
free(classpath);
return pid; return pid;
} }
@@ -937,7 +953,7 @@ run_server(void *data) {
assert(serial); assert(serial);
LOGD("Device serial: %s", serial); LOGD("Device serial: %s", serial);
ok = push_server(&server->intr, serial); ok = push_server(&server->intr, params->scid, serial);
if (!ok) { if (!ok) {
goto error_connection_failed; goto error_connection_failed;
} }

View File

@@ -14,3 +14,8 @@ system = 'windows'
cpu_family = 'x86' cpu_family = 'x86'
cpu = 'i686' cpu = 'i686'
endian = 'little' 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

@@ -14,3 +14,8 @@ system = 'windows'
cpu_family = 'x86' cpu_family = 'x86'
cpu = 'x86_64' cpu = 'x86_64'
endian = 'little' 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

@@ -233,10 +233,10 @@ install` must be run as root)._
#### Option 2: Use prebuilt server #### Option 2: Use prebuilt server
- [`scrcpy-server-v2.3`][direct-scrcpy-server] - [`scrcpy-server-v2.2`][direct-scrcpy-server]
<sub>SHA-256: `8daed514d7796fca6987dc973e201bd15ba51d0f7258973dec92d9ded00dbd5f`</sub> <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 Download the prebuilt server somewhere, and specify its path during the Meson
configuration: 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 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 ## List

View File

@@ -130,7 +130,7 @@ scrcpy --orientation=270 # 270° clockwise
scrcpy --orientation=flip0 # hflip scrcpy --orientation=flip0 # hflip
scrcpy --orientation=flip90 # hflip + 90° clockwise scrcpy --orientation=flip90 # hflip + 90° clockwise
scrcpy --orientation=flip180 # vflip (hflip + 180°) scrcpy --orientation=flip180 # vflip (hflip + 180°)
scrcpy --orientation=flip270 # hflip + 270° clockwise scrcpy --orientation=flip270 # hflip + 270°
``` ```
The orientation can be set separately for display and record if necessary, via The orientation can be set separately for display and record if necessary, via

View File

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

View File

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

View File

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

View File

@@ -69,62 +69,58 @@ prepare-deps:
@app/prebuilt-deps/prepare-libusb.sh @app/prebuilt-deps/prepare-libusb.sh
build-win32: prepare-deps build-win32: prepare-deps
rm -rf "$(WIN32_BUILD_DIR)" [ -d "$(WIN32_BUILD_DIR)" ] || ( mkdir "$(WIN32_BUILD_DIR)" && \
mkdir -p "$(WIN32_BUILD_DIR)/local" meson setup "$(WIN32_BUILD_DIR)" \
cp -r app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-3/win32/. "$(WIN32_BUILD_DIR)/local/" --cross-file cross_win32.txt \
cp -r app/prebuilt-deps/data/SDL2-2.28.5/i686-w64-mingw32/. "$(WIN32_BUILD_DIR)/local/" --buildtype release --strip -Db_lto=true \
cp -r app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-Win32/. "$(WIN32_BUILD_DIR)/local/" -Dcompile_server=false \
meson setup "$(WIN32_BUILD_DIR)" \ -Dportable=true )
--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)" ninja -C "$(WIN32_BUILD_DIR)"
build-win64: prepare-deps build-win64: prepare-deps
rm -rf "$(WIN64_BUILD_DIR)" [ -d "$(WIN64_BUILD_DIR)" ] || ( mkdir "$(WIN64_BUILD_DIR)" && \
mkdir -p "$(WIN64_BUILD_DIR)/local" meson setup "$(WIN64_BUILD_DIR)" \
cp -r app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-3/win64/. "$(WIN64_BUILD_DIR)/local/" --cross-file cross_win64.txt \
cp -r app/prebuilt-deps/data/SDL2-2.28.5/x86_64-w64-mingw32/. "$(WIN64_BUILD_DIR)/local/" --buildtype release --strip -Db_lto=true \
cp -r app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-x64/. "$(WIN64_BUILD_DIR)/local/" -Dcompile_server=false \
meson setup "$(WIN64_BUILD_DIR)" \ -Dportable=true )
--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
ninja -C "$(WIN64_BUILD_DIR)" ninja -C "$(WIN64_BUILD_DIR)"
dist-win32: build-server build-win32 dist-win32: build-server build-win32
mkdir -p "$(DIST)/$(WIN32_TARGET_DIR)" mkdir -p "$(DIST)/$(WIN32_TARGET_DIR)"
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(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 "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/data/scrcpy-console.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/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)"
cp app/data/icon.png "$(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/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/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/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinUsbApi.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 dist-win64: build-server build-win64
mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)" mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)"
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(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 "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/data/scrcpy-console.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/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)"
cp app/data/icon.png "$(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/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/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/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinUsbApi.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 zip-win32: dist-win32
cd "$(DIST)"; \ cd "$(DIST)"; \

View File

@@ -7,8 +7,8 @@ android {
applicationId "com.genymobile.scrcpy" applicationId "com.genymobile.scrcpy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 34 targetSdkVersion 34
versionCode 20300 versionCode 200
versionName "2.3" versionName "v2.2"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {

View File

@@ -12,7 +12,7 @@
set -e set -e
SCRCPY_DEBUG=false SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=2.3 SCRCPY_VERSION_NAME=v2.2
PLATFORM=${ANDROID_PLATFORM:-34} PLATFORM=${ANDROID_PLATFORM:-34}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-34.0.0} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-34.0.0}

View File

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

View File

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

View File

@@ -146,8 +146,6 @@ public final class CleanUp {
} }
public static void main(String... args) { public static void main(String... args) {
unlinkSelf();
try { try {
// Wait for the server to die // Wait for the server to die
System.in.read(); System.in.read();
@@ -185,7 +183,11 @@ public final class CleanUp {
} else if (config.restoreNormalPowerMode) { } else if (config.restoreNormalPowerMode) {
Ln.i("Restoring normal power mode"); Ln.i("Restoring normal power mode");
Device.setScreenPowerMode(Device.POWER_MODE_NORMAL); Device.setScreenPowerMode(Device.POWER_MODE_NORMAL);
// Make sure the request is performed before exiting
DisplayPowerMode.stopAndJoin();
} }
} }
unlinkSelf();
} }
} }

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, MotionEvent event = MotionEvent
DEFAULT_DEVICE_ID, 0, source, 0); .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); 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_HSCROLL, hScroll);
coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll); coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll);
MotionEvent event = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, MotionEvent event = MotionEvent
DEFAULT_DEVICE_ID, 0, InputDevice.SOURCE_MOUSE, 0); .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); return device.injectEvent(event, Device.INJECT_MODE_ASYNC);
} }

View File

@@ -1,7 +1,6 @@
package com.genymobile.scrcpy; package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.ClipboardManager; import com.genymobile.scrcpy.wrappers.ClipboardManager;
import com.genymobile.scrcpy.wrappers.DisplayControl;
import com.genymobile.scrcpy.wrappers.InputManager; import com.genymobile.scrcpy.wrappers.InputManager;
import com.genymobile.scrcpy.wrappers.ServiceManager; import com.genymobile.scrcpy.wrappers.ServiceManager;
import com.genymobile.scrcpy.wrappers.SurfaceControl; import com.genymobile.scrcpy.wrappers.SurfaceControl;
@@ -12,8 +11,8 @@ import android.graphics.Rect;
import android.os.Build; import android.os.Build;
import android.os.IBinder; import android.os.IBinder;
import android.os.SystemClock; import android.os.SystemClock;
import android.view.IDisplayFoldListener;
import android.view.IRotationWatcher; import android.view.IRotationWatcher;
import android.view.IDisplayFoldListener;
import android.view.InputDevice; import android.view.InputDevice;
import android.view.InputEvent; import android.view.InputEvent;
import android.view.KeyCharacterMap; import android.view.KeyCharacterMap;
@@ -316,12 +315,16 @@ public final class Device {
*/ */
public static boolean setScreenPowerMode(int mode) { public static boolean setScreenPowerMode(int mode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// On Android 14, these internal methods have been moved to DisplayControl if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && !SurfaceControl.hasPhysicalDisplayIdsMethod()) {
boolean useDisplayControl = // On Android 14+, these internal methods have been moved to system server classes.
Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && !SurfaceControl.hasPhysicalDisplayIdsMethod(); // Run a separate process with the correct classpath and LD_PRELOAD to change the display power mode.
DisplayPowerMode.setRemoteDisplayPowerMode(mode);
// The call is asynchronous (we don't want to block)
return true;
}
// Change the power mode for all physical displays // Change the power mode for all physical displays
long[] physicalDisplayIds = useDisplayControl ? DisplayControl.getPhysicalDisplayIds() : SurfaceControl.getPhysicalDisplayIds(); long[] physicalDisplayIds = SurfaceControl.getPhysicalDisplayIds();
if (physicalDisplayIds == null) { if (physicalDisplayIds == null) {
Ln.e("Could not get physical display ids"); Ln.e("Could not get physical display ids");
return false; return false;
@@ -329,8 +332,7 @@ public final class Device {
boolean allOk = true; boolean allOk = true;
for (long physicalDisplayId : physicalDisplayIds) { for (long physicalDisplayId : physicalDisplayIds) {
IBinder binder = useDisplayControl ? DisplayControl.getPhysicalDisplayToken( IBinder binder = SurfaceControl.getPhysicalDisplayToken(physicalDisplayId);
physicalDisplayId) : SurfaceControl.getPhysicalDisplayToken(physicalDisplayId);
allOk &= SurfaceControl.setDisplayPowerMode(binder, mode); allOk &= SurfaceControl.setDisplayPowerMode(binder, mode);
} }
return allOk; return allOk;

View File

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

View File

@@ -0,0 +1,161 @@
package com.genymobile.scrcpy;
import android.annotation.SuppressLint;
import android.os.IBinder;
import java.io.IOException;
import java.lang.reflect.Method;
/**
* On Android 14, the methods used to turn the device screen off have been moved from SurfaceControl (in framework.jar) to DisplayControl (a system
* server class). As a consequence, they could not be called directly. See {@url https://github.com/Genymobile/scrcpy/issues/3927}.
* <p>
* Instead, run a separate process with a different classpath and LD_PRELOAD just to set the display power mode.
* <p>
* Since the client must not block, and calling/joining a process is blocking (this specific one takes a few hundred milliseconds to complete),
* this class uses an internal thread to execute the requests asynchronously, and serialize them (so that two successive requests are guaranteed to
* be executed in order). In addition, it only executes the last pending request (setting power mode to value X then to value Y is equivalent to
* just setting it to value Y).
*/
public final class DisplayPowerMode {
private static final Proxy PROXY = new Proxy();
private static final class Proxy implements Runnable {
private Thread thread;
private int requestedMode = -1;
private boolean stopped;
synchronized void requestMode(int mode) {
if (thread == null) {
thread = new Thread(this, "DisplayPowerModeProxy");
thread.setDaemon(true);
thread.start();
}
requestedMode = mode;
notify();
}
void stopAndJoin() {
boolean hasThread;
synchronized (this) {
hasThread = thread != null;
if (thread != null) {
stopped = true;
notify();
}
}
if (hasThread) {
// Join the thread without holding the mutex (that would cause a deadlock)
try {
thread.join();
} catch (InterruptedException e) {
Ln.e("Thread join interrupted", e);
}
}
}
@Override
public void run() {
try {
int mode;
while (true) {
synchronized (this) {
while (!stopped && requestedMode == -1) {
wait();
}
mode = requestedMode;
requestedMode = -1;
}
// Even if stopped, the last request must be executed to restore the display power mode to normal
if (mode == -1) {
return;
}
try {
Process process = executeSystemServerSetDisplayPowerMode(mode);
int status = process.waitFor();
if (status != 0) {
Ln.e("Set display power mode failed remotely: status=" + status);
}
} catch (Exception e) {
Ln.e("Failed to execute process", e);
}
}
} catch (InterruptedException e) {
// end of thread
}
}
}
private DisplayPowerMode() {
// not instantiable
}
// Called from the scrcpy process
public static void setRemoteDisplayPowerMode(int mode) {
PROXY.requestMode(mode);
}
public static void stopAndJoin() {
PROXY.stopAndJoin();
}
// Called from the proxy thread in the scrcpy process
private static Process executeSystemServerSetDisplayPowerMode(int mode) throws IOException {
String[] ldPreloadLibs = {"/system/lib64/libandroid_servers.so"};
String[] cmd = {"app_process", "/", DisplayPowerMode.class.getName(), String.valueOf(mode)};
ProcessBuilder builder = new ProcessBuilder(cmd);
builder.environment().put("LD_PRELOAD", String.join(" ", ldPreloadLibs));
builder.environment().put("CLASSPATH", Server.SERVER_PATH + ":/system/framework/services.jar");
return builder.start();
}
// Executed in the DisplayPowerMode-specific process
@SuppressLint({"PrivateApi", "SoonBlockedPrivateApi"})
private static void setDisplayPowerModeUsingDisplayControl(int mode) throws Exception {
System.loadLibrary("android_servers");
@SuppressLint("PrivateApi")
Class<?> displayControlClass = Class.forName("com.android.server.display.DisplayControl");
Method getPhysicalDisplayIdsMethod = displayControlClass.getDeclaredMethod("getPhysicalDisplayIds");
Method getPhysicalDisplayTokenMethod = displayControlClass.getDeclaredMethod("getPhysicalDisplayToken", long.class);
Class<?> surfaceControlClass = Class.forName("android.view.SurfaceControl");
Method setDisplayPowerModeMethod = surfaceControlClass.getDeclaredMethod("setDisplayPowerMode", IBinder.class, int.class);
long[] displayIds = (long[]) getPhysicalDisplayIdsMethod.invoke(null);
for (long displayId : displayIds) {
Object token = getPhysicalDisplayTokenMethod.invoke(null, displayId);
setDisplayPowerModeMethod.invoke(null, token, mode);
}
}
public static void main(String... args) {
Ln.disableSystemStreams();
Ln.initLogLevel(Ln.Level.DEBUG);
int status = run(args) ? 0 : 1;
System.exit(status);
}
private static boolean run(String... args) {
if (args.length != 1) {
Ln.e("Exactly one argument expected: the value of one of the SurfaceControl.POWER_MODE_* constants (typically 0 or 2)");
return false;
}
try {
int mode = Integer.parseInt(args[0]);
setDisplayPowerModeUsingDisplayControl(mode);
return true;
} catch (Throwable e) {
Ln.e("Could not set display power mode", e);
return false;
}
}
}

View File

@@ -11,7 +11,6 @@ import java.util.List;
public final class Server { public final class Server {
public static final String SERVER_PATH; public static final String SERVER_PATH;
static { static {
String[] classPaths = System.getProperty("java.class.path").split(File.pathSeparator); 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 // By convention, scrcpy is always executed with the absolute path of scrcpy-server.jar as the first item in the classpath

View File

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

View File

@@ -49,7 +49,6 @@ public final class Workarounds {
} }
public static void apply(boolean audio, boolean camera) { public static void apply(boolean audio, boolean camera) {
boolean mustFillConfigurationController = false;
boolean mustFillAppInfo = false; boolean mustFillAppInfo = false;
boolean mustFillAppContext = false; boolean mustFillAppContext = false;
@@ -86,23 +85,11 @@ public final class Workarounds {
mustFillAppContext = true; 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) { if (mustFillAppInfo) {
fillAppInfo(); Workarounds.fillAppInfo();
} }
if (mustFillAppContext) { if (mustFillAppContext) {
fillAppContext(); Workarounds.fillAppContext();
} }
} }
@@ -162,22 +149,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() { static Context getSystemContext() {
try { try {
Method getSystemContextMethod = ACTIVITY_THREAD_CLASS.getDeclaredMethod("getSystemContext"); Method getSystemContextMethod = ACTIVITY_THREAD_CLASS.getDeclaredMethod("getSystemContext");

View File

@@ -138,8 +138,8 @@ public final class ClipboardManager {
} }
} }
private static void addPrimaryClipChangedListener(Method method, int methodVersion, IInterface manager, IOnPrimaryClipChangedListener listener) private static void addPrimaryClipChangedListener(Method method, int methodVersion, IInterface manager,
throws InvocationTargetException, IllegalAccessException { IOnPrimaryClipChangedListener listener) throws InvocationTargetException, IllegalAccessException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
method.invoke(manager, listener, FakeContext.PACKAGE_NAME); method.invoke(manager, listener, FakeContext.PACKAGE_NAME);
return; 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 { private Method getIsScreenOnMethod() throws NoSuchMethodException {
if (isScreenOnMethod == null) { if (isScreenOnMethod == null) {
@SuppressLint("ObsoleteSdkInt") // we may lower minSdkVersion in the future @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); isScreenOnMethod = manager.getClass().getMethod(methodName);
} }
return isScreenOnMethod; return isScreenOnMethod;

View File

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

View File

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