Compare commits

..

33 Commits

Author SHA1 Message Date
Romain Vimont
bd1deffa70 Use current adb port (if any) for --tcpip
If the current adb port is not 5555 (typically 0 because it is not in
TCP/IP mode), --tcpip automatically executes (among other commands):

    adb tcpip 5555

In case adb was already listening on another port, this command forced
to listen on 5555, and the connection should still succeed.

But this reconfiguration might be inconvenient for the user. If adb is
already in TCP/IP mode, use the current enabled port without
reconfiguration.

Fixes #3591 <https://github.com/Genymobile/scrcpy/issues/3591>
2022-12-02 19:09:53 +01:00
Romain Vimont
6469b55861 Fix CommandParserTest code style
Make checkstyle happy.
2022-11-24 09:27:10 +01:00
SeungHoon Han
597703b62e Fix DisplayInfo parsing for Android Q
The DisplayInfo dump format has slightly changed in AOSP:
<1039ea50f3>

PR #3573 <https://github.com/Genymobile/scrcpy/pull/3573>
Ref #3416 <https://github.com/Genymobile/scrcpy/pull/3416>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-11-12 18:15:45 +01:00
Yu-Chen Lin
48bb6f2ea8 Support wchar_t in argv for Windows
PR #3547 <https://github.com/Genymobile/scrcpy/pull/3547>
Fixes #2932 <https://github.com/Genymobile/scrcpy/issues/2932>

Signed-off-by: Yu-Chen Lin <npes87184@gmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-10-23 23:45:00 +02:00
Yu-Chen Lin
d71587e39b Avoid string concatenation in crossfiles
This feature is not supported on older meson versions:

    ERROR: Malformed value in cross file variable prebuilt_libusb.

Refs <https://github.com/mesonbuild/meson/issues/3878>
PR #3546 <https://github.com/Genymobile/scrcpy/pull/3546>

Signed-off-by: Yu-Chen Lin <npes87184@gmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-10-23 12:31:44 +02:00
Romain Vimont
b62424a98a Build log.c for test_cli
On Windows, sc_log_windows_error() is called from net.c, so log.c must
also be compiled.

Fixes #3542 <https://github.com/Genymobile/scrcpy/issues/3542>
2022-10-19 15:17:43 +02:00
Romain Vimont
ffc7b91693 Add missing include <string.h> for strlen() 2022-10-19 15:14:56 +02:00
Romain Vimont
cb46e4a64a Add missing include <string.h> for memmove() 2022-10-19 15:13:55 +02:00
Anima C13
16e2c1ce26 Add -s auto-completion for zsh
Fixes #3522 <https://github.com/Genymobile/scrcpy/pull/3522>
PR #3523 <https://github.com/Genymobile/scrcpy/pull/3523>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-10-12 13:24:43 +02:00
Anima C13
1bfbadef96 Add -s auto-completion for bash
Fixes #3522 <https://github.com/Genymobile/scrcpy/pull/3522>
PR #3523 <https://github.com/Genymobile/scrcpy/pull/3523>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-10-12 13:24:43 +02:00
Romain Vimont
40644994e8 Make ServiceManager and Settings methods static
There were exactly one instance of ServiceManager and Settings, stored
in Device.

Since a Device instance is not created by the CleanUp executable, it was
not straightforward to call wrapper methods on cleanup.

Remove this artificial restriction and expose them publicly via static
methods (this is equivalent to expose a singleton, but less verbose).
2022-10-02 17:57:35 +02:00
Romain Vimont
7505f7117e Fix typo in logs 2022-09-27 14:12:37 +02:00
SeungHoon Han
949b64dff2 Add fallback to get DisplayInfo
PR #3416 <https://github.com/Genymobile/scrcpy/pull/3416>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-09-25 16:22:51 +02:00
Romain Vimont
00e9e69c2a Use /dev/null instead of closing fds
Some adb commands do not like when stdin, stdout or stderr are closed
(they hang forever). Open /dev/null for each.
2022-09-25 15:42:33 +02:00
Romain Vimont
4a5cdcd390 Extract $BUILD_TOOLS_DIR
In the script to build without gradle, the build-tools full path is used
at several places. Use a separate variable for readability.
2022-09-25 14:26:07 +02:00
Romain Vimont
e5e210506f Add scrcpy-console.desktop
Add a launcher which opens a terminal, and keep it open in case of
errors (so that the user has time to read error messages).

The behavior is the same as scrcpy-console.bat on Windows.

PR #3351 <https://github.com/Genymobile/scrcpy/pull/3351>
2022-09-09 19:06:29 +02:00
Chih-Hsuan Yen
a2a22f497f Use shell environment to execute launcher
Make Exec= compatible with $PATH configured in .bashrc or .zshrc…

PR #3351 <https://github.com/Genymobile/scrcpy/pull/3351>
Refs #296 <https://github.com/Genymobile/scrcpy/pull/296#discussion_r224987002>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-09-09 19:06:29 +02:00
Addison Snelling
51a1762cbd Add desktop entry file for Linux app launchers
Refs <https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html>

PR #3351 <https://github.com/Genymobile/scrcpy/pull/3351>
Replaces PR #296 <https://github.com/Genymobile/scrcpy/pull/296>
Fixes #295 <https://github.com/Genymobile/scrcpy/issues/295>
Fixes #748 <https://github.com/Genymobile/scrcpy/issues/748>
Fixes #1636 <https://github.com/Genymobile/scrcpy/issues/1636>

Co-authored-by: Chih-Hsuan Yen <yan12125@gmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-09-09 19:06:29 +02:00
Romain Vimont
c1ec1d1023 Replace hardcoded 'share/' by datadir variable
Meson defines a variable for the data directory.

PR #3351 <https://github.com/Genymobile/scrcpy/pull/3351>
2022-09-09 19:06:29 +02:00
Romain Vimont
0a0a446ea6 Upgrade Android SDK to 33 2022-09-02 14:42:37 +02:00
Romain Vimont
fccfc43b9e Upgrade gradle build tools to 7.2.2
Plugin version 7.2.2.
Gradle version 7.3.3.

Refs: <https://developer.android.com/studio/releases/gradle-plugin#updating-gradle>
2022-09-02 14:40:16 +02:00
Romain Vimont
121bb71dfe Move from jcenter() to mavenCentral()
Refs <https://developer.android.com/studio/build/jcenter-migration>
Refs <https://jfrog.com/blog/into-the-sunset-bintray-jcenter-gocenter-and-chartcenter/>
2022-09-02 14:31:15 +02:00
Simon Chan
57056d078d Use precise scrolling values
Since SDL 2.0.18, the amount scrolled horizontally or vertically is
exposed as a float (between 0 and 1). Forward a precise value to the
Android device when possible.

Refs <https://wiki.libsdl.org/SDL_MouseWheelEvent>
Fixes #3363 <https://github.com/Genymobile/scrcpy/issues/3363>
PR #3369 <https://github.com/Genymobile/scrcpy/pull/3369>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-08-28 15:23:08 +02:00
Romain Vimont
1f138aef41 Add conversion from float to fixed-point i16
To encode float values between -1 and 1.

PR #3369 <https://github.com/Genymobile/scrcpy/pull/3369>
2022-08-28 15:23:08 +02:00
Romain Vimont
1ab6c19486 Add unit test for float encoding
PR #3369 <https://github.com/Genymobile/scrcpy/pull/3369>
2022-08-28 15:23:08 +02:00
Romain Vimont
fd3483c837 Extract conversion from float to u16 fixed-point
PR #3369 <https://github.com/Genymobile/scrcpy/pull/3369>
2022-08-28 15:23:08 +02:00
Romain Vimont
041cdf6cf5 Rename buffer_util.h to binary.h
It will allow to expose more binary util functions not related to
buffers.

PR #3369 <https://github.com/Genymobile/scrcpy/pull/3369>
2022-08-28 15:23:08 +02:00
Romain Vimont
136ab8c199 Add unit test for float decoding
PR #3369 <https://github.com/Genymobile/scrcpy/pull/3369>
2022-08-28 15:23:08 +02:00
Romain Vimont
3848ce86f1 Extract conversion from u16 fixed-point to float
PR #3369 <https://github.com/Genymobile/scrcpy/pull/3369>
2022-08-28 15:23:08 +02:00
Romain Vimont
5b8e9aa0e9 Move toUnsigned() to a Binary util class
PR #3369 <https://github.com/Genymobile/scrcpy/pull/3369>
2022-08-28 15:23:08 +02:00
Romain Vimont
3a66b5fd01 Remove deprecated meson.source_root()
This method is deprecated since Meson 0.56.0:
<https://mesonbuild.com/Release-notes-for-0-56-0.html#mesonbuild_root-and-mesonsource_root-are-deprecated>

We could replace it with meson.project_source_root(), but this would
make Meson 0.56 or above mandatory. Since the path in always computed
from the server/ directory, just add '..' to reference the root project
directory.

Refs c456e38264
2022-08-28 15:16:31 +02:00
Derek Wu
9c1722f428 Use DisplayManagerGlobal instance
Use the client instance to communicate with the DisplayManager server.

Fixes #3446 <https://github.com/Genymobile/scrcpy/issues/3446>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-08-19 18:03:38 +02:00
Romain Vimont
d19606eb0c Rename net_listen() parameter
For consistency with net_accept(), which necessarily uses a server
socket, name the net_listen() parameter "server_socket".
2022-08-17 16:40:45 +02:00
34 changed files with 583 additions and 144 deletions

View File

@@ -395,8 +395,8 @@ address), connect the device over USB, then run:
scrcpy --tcpip # without arguments
```
It will automatically find the device IP address, enable TCP/IP mode, then
connect to the device before starting.
It will automatically find the device IP address and adb port, enable TCP/IP
mode if necessary, then connect to the device before starting.
##### Manual

View File

@@ -93,6 +93,11 @@ _scrcpy() {
COMPREPLY=($(compgen -W 'verbose debug info warn error' -- "$cur"))
return
;;
-s|--serial)
# Use 'adb devices' to list serial numbers
COMPREPLY=($(compgen -W "$("${ADB:-adb}" devices | awk '$2 == "device" {print $1}')" -- ${cur}))
return
;;
-b|--bitrate \
|--codec-options \
|--crop \
@@ -103,7 +108,6 @@ _scrcpy() {
|-m|--max-size \
|-p|--port \
|--push-target \
|-s|--serial \
|--tunnel-host \
|--tunnel-port \
|--v4l2-buffer \

View File

@@ -0,0 +1,13 @@
[Desktop Entry]
Name=scrcpy (console)
GenericName=Android Remote Control
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/bash --norc --noprofile -i -c '"$SHELL" -i -c scrcpy || read -p "Press any key to quit..."'
Icon=scrcpy
Terminal=true
Type=Application
Categories=Utility;RemoteAccess;
StartupNotify=false

13
app/data/scrcpy.desktop Normal file
View File

@@ -0,0 +1,13 @@
[Desktop Entry]
Name=scrcpy
GenericName=Android Remote Control
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'
Icon=scrcpy
Terminal=false
Type=Application
Categories=Utility;RemoteAccess;
StartupNotify=false

View File

@@ -47,7 +47,7 @@ arguments=(
'--record-format=[Force recording format]:format:(mp4 mkv)'
'--render-driver=[Request SDL to use the given render driver]:driver name:(direct3d opengl opengles2 opengles metal software)'
'--rotation=[Set the initial display rotation]:rotation values:(0 1 2 3)'
{-s,--serial=}'[The device serial number \(mandatory for multiple devices only\)]'
{-s,--serial=}'[The device serial number \(mandatory for multiple devices only\)]:serial:($("${ADB-adb}" devices | awk '\''$2 == "device" {print $1}'\''))'
'--shortcut-mod=[\[key1,key2+key3,...\] Specify the modifiers to use for scrcpy shortcuts]:shortcut mod:(lctrl rctrl lalt ralt lsuper rsuper)'
{-S,--turn-screen-off}'[Turn the device screen off immediately]'
{-t,--show-touches}'[Show physical touches]'

View File

@@ -223,14 +223,26 @@ executable('scrcpy', src,
install: true,
c_args: [])
# <https://mesonbuild.com/Builtin-options.html#directories>
datadir = get_option('datadir') # by default 'share'
install_man('scrcpy.1')
install_data('data/icon.png',
rename: 'scrcpy.png',
install_dir: 'share/icons/hicolor/256x256/apps')
install_dir: join_paths(datadir, 'icons/hicolor/256x256/apps'))
install_data('data/zsh-completion/_scrcpy',
install_dir: 'share/zsh/site-functions')
install_dir: join_paths(datadir, 'zsh/site-functions'))
install_data('data/bash-completion/scrcpy',
install_dir: 'share/bash-completion/completions')
install_dir: join_paths(datadir, 'bash-completion/completions'))
# Desktop entry file for application launchers
if host_machine.system() == 'linux'
# Install a launcher (ex: /usr/local/share/applications/scrcpy.desktop)
install_data('data/scrcpy.desktop',
install_dir: join_paths(datadir, 'applications'))
install_data('data/scrcpy-console.desktop',
install_dir: join_paths(datadir, 'applications'))
endif
### TESTS
@@ -255,6 +267,7 @@ if get_option('buildtype') == 'debug'
'tests/test_cli.c',
'src/cli.c',
'src/options.c',
'src/util/log.c',
'src/util/net.c',
'src/util/str.c',
'src/util/strbuf.c',

View File

@@ -275,7 +275,7 @@ Configure and reconnect the device over TCP/IP.
If a destination address is provided, then scrcpy connects to this address before starting. The device must listen on the given TCP port (default is 5555).
If no destination address is provided, then scrcpy attempts to find the IP address of the current device (typically connected over USB), enables TCP/IP mode, then connects to this address before starting.
If no destination address is provided, then scrcpy attempts to find the IP address and adb port of the current device (typically connected over USB), enables TCP/IP mode if necessary, then connects to this address before starting.
.TP
.B \-S, \-\-turn\-screen\-off

View File

@@ -37,7 +37,7 @@ static const char *const android_motionevent_action_labels[] = {
"move",
"cancel",
"outside",
"ponter-down",
"pointer-down",
"pointer-up",
"hover-move",
"scroll",

View File

@@ -4,6 +4,10 @@
#include <stdbool.h>
#include <unistd.h>
#include <libavformat/avformat.h>
#ifdef _WIN32
#include <windows.h>
#include "util/str.h"
#endif
#ifdef HAVE_V4L2
# include <libavdevice/avdevice.h>
#endif
@@ -18,8 +22,8 @@
#include "version.h"
int
main(int argc, char *argv[]) {
#ifdef __WINDOWS__
main_scrcpy(int argc, char *argv[]) {
#ifdef _WIN32
// disable buffering, we want logs immediately
// even line buffering (setvbuf() with mode _IOLBF) is not sufficient
setbuf(stdout, NULL);
@@ -80,3 +84,52 @@ main(int argc, char *argv[]) {
return ret;
}
int
main(int argc, char *argv[]) {
#ifndef _WIN32
return main_scrcpy(argc, argv);
#else
(void) argc;
(void) argv;
int wargc;
wchar_t **wargv = CommandLineToArgvW(GetCommandLineW(), &wargc);
if (!wargv) {
LOG_OOM();
return SCRCPY_EXIT_FAILURE;
}
char **argv_utf8 = malloc((wargc + 1) * sizeof(*argv_utf8));
if (!argv_utf8) {
LOG_OOM();
LocalFree(wargv);
return SCRCPY_EXIT_FAILURE;
}
argv_utf8[wargc] = NULL;
for (int i = 0; i < wargc; ++i) {
argv_utf8[i] = sc_str_from_wchars(wargv[i]);
if (!argv_utf8[i]) {
LOG_OOM();
for (int j = 0; j < i; ++j) {
free(argv_utf8[j]);
}
LocalFree(wargv);
free(argv_utf8);
return SCRCPY_EXIT_FAILURE;
}
}
LocalFree(wargv);
int ret = main_scrcpy(wargc, argv_utf8);
for (int i = 0; i < wargc; ++i) {
free(argv_utf8[i]);
}
free(argv_utf8);
return ret;
#endif
}

View File

@@ -19,6 +19,8 @@
#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
static char *
get_server_path(void) {
#ifdef __WINDOWS__
@@ -513,27 +515,36 @@ sc_server_on_terminated(void *userdata) {
LOGD("Server terminated");
}
static bool
is_tcpip_mode_enabled(struct sc_server *server, const char *serial) {
static uint16_t
get_adb_tcp_port(struct sc_server *server, const char *serial) {
struct sc_intr *intr = &server->intr;
char *current_port =
sc_adb_getprop(intr, serial, "service.adb.tcp.port", SC_ADB_SILENT);
if (!current_port) {
return false;
return 0;
}
// Is the device is listening on TCP on port 5555?
bool enabled = !strcmp("5555", current_port);
long value;
bool ok = sc_str_parse_integer(current_port, &value);
free(current_port);
return enabled;
if (!ok) {
return 0;
}
if (value < 0 || value > 0xFFFF) {
return 0;
}
return value;
}
static bool
wait_tcpip_mode_enabled(struct sc_server *server, const char *serial,
unsigned attempts, sc_tick delay) {
if (is_tcpip_mode_enabled(server, serial)) {
LOGI("TCP/IP mode enabled");
uint16_t expected_port, unsigned attempts,
sc_tick delay) {
uint16_t adb_port = get_adb_tcp_port(server, serial);
if (adb_port == expected_port) {
return true;
}
@@ -547,28 +558,23 @@ wait_tcpip_mode_enabled(struct sc_server *server, const char *serial,
return false;
}
if (is_tcpip_mode_enabled(server, serial)) {
LOGI("TCP/IP mode enabled");
adb_port = get_adb_tcp_port(server, serial);
if (adb_port == expected_port) {
return true;
}
} while (--attempts);
return false;
}
char *
append_port_5555(const char *ip) {
size_t len = strlen(ip);
// sizeof counts the final '\0'
char *ip_port = malloc(len + sizeof(":5555"));
if (!ip_port) {
static char *
append_port(const char *ip, uint16_t port) {
char *ip_port;
int ret = asprintf(&ip_port, "%s:%" PRIu16, ip, port);
if (ret == -1) {
LOG_OOM();
return NULL;
}
memcpy(ip_port, ip, len);
memcpy(ip_port + len, ":5555", sizeof(":5555"));
return ip_port;
}
@@ -586,34 +592,36 @@ sc_server_switch_to_tcpip(struct sc_server *server, const char *serial) {
return NULL;
}
char *ip_port = append_port_5555(ip);
free(ip);
if (!ip_port) {
return NULL;
}
uint16_t adb_port = get_adb_tcp_port(server, serial);
if (adb_port) {
LOGI("TCP/IP mode already enabled on port %" PRIu16, adb_port);
} else {
LOGI("Enabling TCP/IP mode on port " SC_STR(SC_ADB_PORT_DEFAULT) "...");
bool tcp_mode = is_tcpip_mode_enabled(server, serial);
if (!tcp_mode) {
bool ok = sc_adb_tcpip(intr, serial, 5555, SC_ADB_NO_STDOUT);
bool ok = sc_adb_tcpip(intr, serial, SC_ADB_PORT_DEFAULT,
SC_ADB_NO_STDOUT);
if (!ok) {
LOGE("Could not restart adbd in TCP/IP mode");
goto error;
free(ip);
return NULL;
}
unsigned attempts = 40;
sc_tick delay = SC_TICK_FROM_MS(250);
ok = wait_tcpip_mode_enabled(server, serial, attempts, delay);
ok = wait_tcpip_mode_enabled(server, serial, SC_ADB_PORT_DEFAULT,
attempts, delay);
if (!ok) {
goto error;
free(ip);
return NULL;
}
adb_port = SC_ADB_PORT_DEFAULT;
LOGI("TCP/IP mode enabled on port " SC_STR(SC_ADB_PORT_DEFAULT));
}
char *ip_port = append_port(ip, adb_port);
free(ip);
return ip_port;
error:
free(ip_port);
return NULL;
}
static bool
@@ -640,7 +648,8 @@ sc_server_configure_tcpip_known_address(struct sc_server *server,
const char *addr) {
// Append ":5555" if no port is present
bool contains_port = strchr(addr, ':');
char *ip_port = contains_port ? strdup(addr) : append_port_5555(addr);
char *ip_port = contains_port ? strdup(addr)
: append_port(addr, SC_ADB_PORT_DEFAULT);
if (!ip_port) {
LOG_OOM();
return false;

View File

@@ -92,8 +92,14 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags,
close(in[0]);
}
close(in[1]);
} else {
int devnull = open("/dev/null", O_RDONLY | O_CREAT, 0666);
if (devnull != -1) {
dup2(devnull, STDIN_FILENO);
} else {
LOGE("Could not open /dev/null for stdin");
}
}
// Do not close stdin in the child process, this makes adb fail on Linux
if (pout) {
if (out[1] != STDOUT_FILENO) {
@@ -102,8 +108,12 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags,
}
close(out[0]);
} else if (!inherit_stdout) {
// Close stdout in the child process
close(STDOUT_FILENO);
int devnull = open("/dev/null", O_WRONLY | O_CREAT, 0666);
if (devnull != -1) {
dup2(devnull, STDOUT_FILENO);
} else {
LOGE("Could not open /dev/null for stdout");
}
}
if (perr) {
@@ -113,8 +123,12 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags,
}
close(err[0]);
} else if (!inherit_stderr) {
// Close stderr in the child process
close(STDERR_FILENO);
int devnull = open("/dev/null", O_WRONLY | O_CREAT, 0666);
if (devnull != -1) {
dup2(devnull, STDERR_FILENO);
} else {
LOGE("Could not open /dev/null for stderr");
}
}
close(internal[0]);

View File

@@ -159,8 +159,8 @@ net_connect(sc_socket socket, uint32_t addr, uint16_t port) {
}
bool
net_listen(sc_socket socket, uint32_t addr, uint16_t port, int backlog) {
sc_raw_socket raw_sock = unwrap(socket);
net_listen(sc_socket server_socket, uint32_t addr, uint16_t port, int backlog) {
sc_raw_socket raw_sock = unwrap(server_socket);
int reuse = 1;
if (setsockopt(raw_sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse,

View File

@@ -39,7 +39,7 @@ bool
net_connect(sc_socket socket, uint32_t addr, uint16_t port);
bool
net_listen(sc_socket socket, uint32_t addr, uint16_t port, int backlog);
net_listen(sc_socket server_socket, uint32_t addr, uint16_t port, int backlog);
sc_socket
net_accept(sc_socket server_socket);

View File

@@ -15,14 +15,14 @@ net_connect_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr,
}
bool
net_listen_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr,
net_listen_intr(struct sc_intr *intr, sc_socket server_socket, uint32_t addr,
uint16_t port, int backlog) {
if (!sc_intr_set_socket(intr, socket)) {
if (!sc_intr_set_socket(intr, server_socket)) {
// Already interrupted
return false;
}
bool ret = net_listen(socket, addr, port, backlog);
bool ret = net_listen(server_socket, addr, port, backlog);
sc_intr_set_socket(intr, SC_SOCKET_NONE);
return ret;

View File

@@ -11,7 +11,7 @@ net_connect_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr,
uint16_t port);
bool
net_listen_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr,
net_listen_intr(struct sc_intr *intr, sc_socket server_socket, uint32_t addr,
uint16_t port, int backlog);
sc_socket

View File

@@ -6,6 +6,10 @@
#include <stdbool.h>
#include <stddef.h>
/* Stringify a numeric value */
#define SC_STR(s) SC_XSTR(s)
#define SC_XSTR(s) #s
/**
* Like strncpy(), except:
* - it copies at most n-1 chars

View File

@@ -1,6 +1,7 @@
#include "thread.h"
#include <assert.h>
#include <string.h>
#include <SDL2/SDL_thread.h>
#include "log.h"

View File

@@ -6,6 +6,7 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
// Adapted from vlc_vector:
// <https://code.videolan.org/videolan/vlc/-/blob/0857947abaed9c89810cd96353aaa1b7e6ba3b0d/include/vlc_vector.h>

View File

@@ -4,10 +4,10 @@ buildscript {
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.3'
classpath 'com.android.tools.build:gradle:7.2.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@@ -17,7 +17,7 @@ buildscript {
allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
tasks.withType(JavaCompile) {
options.compilerArgs << "-Xlint:deprecation"

View File

@@ -22,4 +22,4 @@ ffmpeg_avutil = 'avutil-56'
prebuilt_ffmpeg = 'ffmpeg-win32-4.3.1'
prebuilt_sdl2 = 'SDL2-2.0.22/i686-w64-mingw32'
prebuilt_libusb_root = 'libusb-1.0.26'
prebuilt_libusb = prebuilt_libusb_root + '/MinGW-Win32'
prebuilt_libusb = 'libusb-1.0.26/MinGW-Win32'

View File

@@ -22,4 +22,4 @@ ffmpeg_avutil = 'avutil-57'
prebuilt_ffmpeg = 'ffmpeg-win64-5.0.1'
prebuilt_sdl2 = 'SDL2-2.0.22/x86_64-w64-mingw32'
prebuilt_libusb_root = 'libusb-1.0.26'
prebuilt_libusb = prebuilt_libusb_root + '/MinGW-x64'
prebuilt_libusb = 'libusb-1.0.26/MinGW-x64'

View File

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

View File

@@ -1,11 +1,11 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 31
compileSdkVersion 33
defaultConfig {
applicationId "com.genymobile.scrcpy"
minSdkVersion 21
targetSdkVersion 31
targetSdkVersion 33
versionCode 12400
versionName "1.24"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

View File

@@ -14,8 +14,9 @@ set -e
SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=1.24
PLATFORM=${ANDROID_PLATFORM:-31}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-31.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})"
CLASSES_DIR="$BUILD_DIR/classes"
@@ -41,9 +42,8 @@ EOF
echo "Generating java from aidl..."
cd "$SERVER_DIR/src/main/aidl"
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/aidl" -o"$CLASSES_DIR" \
android/view/IRotationWatcher.aidl
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/aidl" -o"$CLASSES_DIR" \
"$BUILD_TOOLS_DIR/aidl" -o"$CLASSES_DIR" android/view/IRotationWatcher.aidl
"$BUILD_TOOLS_DIR/aidl" -o"$CLASSES_DIR" \
android/content/IOnPrimaryClipChangedListener.aidl
echo "Compiling java sources..."
@@ -59,8 +59,7 @@ cd "$CLASSES_DIR"
if [[ $PLATFORM -lt 31 ]]
then
# use dx
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/dx" --dex \
--output "$BUILD_DIR/classes.dex" \
"$BUILD_TOOLS_DIR/dx" --dex --output "$BUILD_DIR/classes.dex" \
android/view/*.class \
android/content/*.class \
com/genymobile/scrcpy/*.class \
@@ -72,7 +71,7 @@ then
rm -rf classes.dex classes
else
# use d8
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/d8" --classpath "$ANDROID_JAR" \
"$BUILD_TOOLS_DIR/d8" --classpath "$ANDROID_JAR" \
--output "$BUILD_DIR/classes.zip" \
android/view/*.class \
android/content/*.class \

View File

@@ -13,8 +13,8 @@ if prebuilt_server == ''
install_dir: 'share/scrcpy')
else
if not prebuilt_server.startswith('/')
# relative path needs some trick
prebuilt_server = meson.source_root() + '/' + prebuilt_server
# prebuilt server path is relative to the root scrcpy directory
prebuilt_server = '../' + prebuilt_server
endif
custom_target('scrcpy-server-prebuilt',
input: prebuilt_server,

View File

@@ -1,7 +1,5 @@
package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.ServiceManager;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Base64;
@@ -164,12 +162,10 @@ public final class CleanUp {
Config config = Config.fromBase64(args[0]);
if (config.disableShowTouches || config.restoreStayOn != -1) {
ServiceManager serviceManager = new ServiceManager();
Settings settings = new Settings(serviceManager);
if (config.disableShowTouches) {
Ln.i("Disabling \"show touches\"");
try {
settings.putValue(Settings.TABLE_SYSTEM, "show_touches", "0");
Settings.putValue(Settings.TABLE_SYSTEM, "show_touches", "0");
} catch (SettingsException e) {
Ln.e("Could not restore \"show_touches\"", e);
}
@@ -177,7 +173,7 @@ public final class CleanUp {
if (config.restoreStayOn != -1) {
Ln.i("Restoring \"stay awake\"");
try {
settings.putValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(config.restoreStayOn));
Settings.putValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(config.restoreStayOn));
} catch (SettingsException e) {
Ln.e("Could not restore \"stay_on_while_plugged_in\"", e);
}

View File

@@ -30,4 +30,14 @@ public final class Command {
}
return result;
}
public static String execReadOutput(String... cmd) throws IOException, InterruptedException {
Process process = Runtime.getRuntime().exec(cmd);
String output = IO.toString(process.getInputStream());
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new IOException("Command " + Arrays.toString(cmd) + " returned with value " + exitCode);
}
return output;
}
}

View File

@@ -31,9 +31,6 @@ public final class Device {
public static final int LOCK_VIDEO_ORIENTATION_UNLOCKED = -1;
public static final int LOCK_VIDEO_ORIENTATION_INITIAL = -2;
private static final ServiceManager SERVICE_MANAGER = new ServiceManager();
private static final Settings SETTINGS = new Settings(SERVICE_MANAGER);
public interface RotationListener {
void onRotationChanged(int rotation);
}
@@ -66,9 +63,9 @@ public final class Device {
public Device(Options options) {
displayId = options.getDisplayId();
DisplayInfo displayInfo = SERVICE_MANAGER.getDisplayManager().getDisplayInfo(displayId);
DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId);
if (displayInfo == null) {
int[] displayIds = SERVICE_MANAGER.getDisplayManager().getDisplayIds();
int[] displayIds = ServiceManager.getDisplayManager().getDisplayIds();
throw new InvalidDisplayIdException(displayId, displayIds);
}
@@ -82,7 +79,7 @@ public final class Device {
screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), deviceSize, crop, maxSize, lockVideoOrientation);
layerStack = displayInfo.getLayerStack();
SERVICE_MANAGER.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() {
ServiceManager.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() {
@Override
public void onRotationChanged(int rotation) {
synchronized (Device.this) {
@@ -98,7 +95,7 @@ public final class Device {
if (options.getControl() && options.getClipboardAutosync()) {
// If control and autosync are enabled, synchronize Android clipboard to the computer automatically
ClipboardManager clipboardManager = SERVICE_MANAGER.getClipboardManager();
ClipboardManager clipboardManager = ServiceManager.getClipboardManager();
if (clipboardManager != null) {
clipboardManager.addPrimaryClipChangedListener(new IOnPrimaryClipChangedListener.Stub() {
@Override
@@ -192,7 +189,7 @@ public final class Device {
return false;
}
return SERVICE_MANAGER.getInputManager().injectInputEvent(inputEvent, injectMode);
return ServiceManager.getInputManager().injectInputEvent(inputEvent, injectMode);
}
public boolean injectEvent(InputEvent event, int injectMode) {
@@ -220,7 +217,7 @@ public final class Device {
}
public static boolean isScreenOn() {
return SERVICE_MANAGER.getPowerManager().isScreenOn();
return ServiceManager.getPowerManager().isScreenOn();
}
public synchronized void setRotationListener(RotationListener rotationListener) {
@@ -232,19 +229,19 @@ public final class Device {
}
public static void expandNotificationPanel() {
SERVICE_MANAGER.getStatusBarManager().expandNotificationsPanel();
ServiceManager.getStatusBarManager().expandNotificationsPanel();
}
public static void expandSettingsPanel() {
SERVICE_MANAGER.getStatusBarManager().expandSettingsPanel();
ServiceManager.getStatusBarManager().expandSettingsPanel();
}
public static void collapsePanels() {
SERVICE_MANAGER.getStatusBarManager().collapsePanels();
ServiceManager.getStatusBarManager().collapsePanels();
}
public static String getClipboardText() {
ClipboardManager clipboardManager = SERVICE_MANAGER.getClipboardManager();
ClipboardManager clipboardManager = ServiceManager.getClipboardManager();
if (clipboardManager == null) {
return null;
}
@@ -256,7 +253,7 @@ public final class Device {
}
public boolean setClipboardText(String text) {
ClipboardManager clipboardManager = SERVICE_MANAGER.getClipboardManager();
ClipboardManager clipboardManager = ServiceManager.getClipboardManager();
if (clipboardManager == null) {
return false;
}
@@ -299,7 +296,7 @@ public final class Device {
* Disable auto-rotation (if enabled), set the screen rotation and re-enable auto-rotation (if it was enabled).
*/
public static void rotateDevice() {
WindowManager wm = SERVICE_MANAGER.getWindowManager();
WindowManager wm = ServiceManager.getWindowManager();
boolean accelerometerRotation = !wm.isRotationFrozen();
@@ -315,8 +312,4 @@ public final class Device {
wm.thawRotation();
}
}
public static Settings getSettings() {
return SETTINGS;
}
}

View File

@@ -6,7 +6,9 @@ import android.system.OsConstants;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.Scanner;
public final class IO {
private IO() {
@@ -37,4 +39,13 @@ public final class IO {
public static void writeFully(FileDescriptor fd, byte[] buffer, int offset, int len) throws IOException {
writeFully(fd, ByteBuffer.wrap(buffer, offset, len));
}
public static String toString(InputStream inputStream) {
StringBuilder builder = new StringBuilder();
Scanner scanner = new Scanner(inputStream);
while (scanner.hasNextLine()) {
builder.append(scanner.nextLine()).append('\n');
}
return builder.toString();
}
}

View File

@@ -20,10 +20,9 @@ public final class Server {
int restoreStayOn = -1;
boolean restoreNormalPowerMode = options.getControl(); // only restore power mode if control is enabled
if (options.getShowTouches() || options.getStayAwake()) {
Settings settings = Device.getSettings();
if (options.getShowTouches()) {
try {
String oldValue = settings.getAndPutValue(Settings.TABLE_SYSTEM, "show_touches", "1");
String oldValue = Settings.getAndPutValue(Settings.TABLE_SYSTEM, "show_touches", "1");
// If "show touches" was disabled, it must be disabled back on clean up
mustDisableShowTouchesOnCleanUp = !"1".equals(oldValue);
} catch (SettingsException e) {
@@ -34,7 +33,7 @@ public final class Server {
if (options.getStayAwake()) {
int stayOn = BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB | BatteryManager.BATTERY_PLUGGED_WIRELESS;
try {
String oldValue = settings.getAndPutValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(stayOn));
String oldValue = Settings.getAndPutValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(stayOn));
try {
restoreStayOn = Integer.parseInt(oldValue);
if (restoreStayOn == stayOn) {

View File

@@ -7,16 +7,14 @@ import android.os.Build;
import java.io.IOException;
public class Settings {
public final class Settings {
public static final String TABLE_SYSTEM = ContentProvider.TABLE_SYSTEM;
public static final String TABLE_SECURE = ContentProvider.TABLE_SECURE;
public static final String TABLE_GLOBAL = ContentProvider.TABLE_GLOBAL;
private final ServiceManager serviceManager;
public Settings(ServiceManager serviceManager) {
this.serviceManager = serviceManager;
private Settings() {
/* not instantiable */
}
private static void execSettingsPut(String table, String key, String value) throws SettingsException {
@@ -35,10 +33,10 @@ public class Settings {
}
}
public String getValue(String table, String key) throws SettingsException {
public static String getValue(String table, String key) throws SettingsException {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
// on Android >= 12, it always fails: <https://github.com/Genymobile/scrcpy/issues/2788>
try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) {
try (ContentProvider provider = ServiceManager.getActivityManager().createSettingsProvider()) {
return provider.getValue(table, key);
} catch (SettingsException e) {
Ln.w("Could not get settings value via ContentProvider, fallback to settings process", e);
@@ -48,10 +46,10 @@ public class Settings {
return execSettingsGet(table, key);
}
public void putValue(String table, String key, String value) throws SettingsException {
public static void putValue(String table, String key, String value) throws SettingsException {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
// on Android >= 12, it always fails: <https://github.com/Genymobile/scrcpy/issues/2788>
try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) {
try (ContentProvider provider = ServiceManager.getActivityManager().createSettingsProvider()) {
provider.putValue(table, key, value);
} catch (SettingsException e) {
Ln.w("Could not put settings value via ContentProvider, fallback to settings process", e);
@@ -61,10 +59,10 @@ public class Settings {
execSettingsPut(table, key, value);
}
public String getAndPutValue(String table, String key, String value) throws SettingsException {
public static String getAndPutValue(String table, String key, String value) throws SettingsException {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
// on Android >= 12, it always fails: <https://github.com/Genymobile/scrcpy/issues/2788>
try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) {
try (ContentProvider provider = ServiceManager.getActivityManager().createSettingsProvider()) {
String oldValue = provider.getValue(table, key);
if (!value.equals(oldValue)) {
provider.putValue(table, key, value);

View File

@@ -1,22 +1,78 @@
package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.Command;
import com.genymobile.scrcpy.DisplayInfo;
import com.genymobile.scrcpy.Ln;
import com.genymobile.scrcpy.Size;
import android.os.IInterface;
import android.view.Display;
import java.lang.reflect.Field;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class DisplayManager {
private final IInterface manager;
private final Object manager; // instance of hidden class android.hardware.display.DisplayManagerGlobal
public DisplayManager(IInterface manager) {
public DisplayManager(Object manager) {
this.manager = manager;
}
// public to call it from unit tests
public static DisplayInfo parseDisplayInfo(String dumpsysDisplayOutput, int displayId) {
Pattern regex = Pattern.compile(
"^ mOverrideDisplayInfo=DisplayInfo\\{\".*?, displayId " + displayId + ".*?(, FLAG_.*)?, real ([0-9]+) x ([0-9]+).*?, "
+ "rotation ([0-9]+).*?, layerStack ([0-9]+)",
Pattern.MULTILINE);
Matcher m = regex.matcher(dumpsysDisplayOutput);
if (!m.find()) {
return null;
}
int flags = parseDisplayFlags(m.group(1));
int width = Integer.parseInt(m.group(2));
int height = Integer.parseInt(m.group(3));
int rotation = Integer.parseInt(m.group(4));
int layerStack = Integer.parseInt(m.group(5));
return new DisplayInfo(displayId, new Size(width, height), rotation, layerStack, flags);
}
private static DisplayInfo getDisplayInfoFromDumpsysDisplay(int displayId) {
try {
String dumpsysDisplayOutput = Command.execReadOutput("dumpsys", "display");
return parseDisplayInfo(dumpsysDisplayOutput, displayId);
} catch (Exception e) {
Ln.e("Could not get display info from \"dumpsys display\" output", e);
return null;
}
}
private static int parseDisplayFlags(String text) {
Pattern regex = Pattern.compile("FLAG_[A-Z_]+");
if (text == null) {
return 0;
}
int flags = 0;
Matcher m = regex.matcher(text);
while (m.find()) {
String flagString = m.group();
try {
Field filed = Display.class.getDeclaredField(flagString);
flags |= filed.getInt(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
// Silently ignore, some flags reported by "dumpsys display" are @TestApi
}
}
return flags;
}
public DisplayInfo getDisplayInfo(int displayId) {
try {
Object displayInfo = manager.getClass().getMethod("getDisplayInfo", int.class).invoke(manager, displayId);
if (displayInfo == null) {
return null;
// fallback when displayInfo is null
return getDisplayInfoFromDumpsysDisplay(displayId);
}
Class<?> cls = displayInfo.getClass();
// width and height already take the rotation into account

View File

@@ -13,27 +13,30 @@ public final class ServiceManager {
public static final String PACKAGE_NAME = "com.android.shell";
public static final int USER_ID = 0;
private final Method getServiceMethod;
private WindowManager windowManager;
private DisplayManager displayManager;
private InputManager inputManager;
private PowerManager powerManager;
private StatusBarManager statusBarManager;
private ClipboardManager clipboardManager;
private ActivityManager activityManager;
public ServiceManager() {
private static final Method GET_SERVICE_METHOD;
static {
try {
getServiceMethod = Class.forName("android.os.ServiceManager").getDeclaredMethod("getService", String.class);
GET_SERVICE_METHOD = Class.forName("android.os.ServiceManager").getDeclaredMethod("getService", String.class);
} catch (Exception e) {
throw new AssertionError(e);
}
}
private IInterface getService(String service, String type) {
private static WindowManager windowManager;
private static DisplayManager displayManager;
private static InputManager inputManager;
private static PowerManager powerManager;
private static StatusBarManager statusBarManager;
private static ClipboardManager clipboardManager;
private static ActivityManager activityManager;
private ServiceManager() {
/* not instantiable */
}
private static IInterface getService(String service, String type) {
try {
IBinder binder = (IBinder) getServiceMethod.invoke(null, service);
IBinder binder = (IBinder) GET_SERVICE_METHOD.invoke(null, service);
Method asInterfaceMethod = Class.forName(type + "$Stub").getMethod("asInterface", IBinder.class);
return (IInterface) asInterfaceMethod.invoke(null, binder);
} catch (Exception e) {
@@ -41,21 +44,28 @@ public final class ServiceManager {
}
}
public WindowManager getWindowManager() {
public static WindowManager getWindowManager() {
if (windowManager == null) {
windowManager = new WindowManager(getService("window", "android.view.IWindowManager"));
}
return windowManager;
}
public DisplayManager getDisplayManager() {
public static DisplayManager getDisplayManager() {
if (displayManager == null) {
displayManager = new DisplayManager(getService("display", "android.hardware.display.IDisplayManager"));
try {
Class<?> clazz = Class.forName("android.hardware.display.DisplayManagerGlobal");
Method getInstanceMethod = clazz.getDeclaredMethod("getInstance");
Object dmg = getInstanceMethod.invoke(null);
displayManager = new DisplayManager(dmg);
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new AssertionError(e);
}
}
return displayManager;
}
public InputManager getInputManager() {
public static InputManager getInputManager() {
if (inputManager == null) {
try {
Method getInstanceMethod = android.hardware.input.InputManager.class.getDeclaredMethod("getInstance");
@@ -68,21 +78,21 @@ public final class ServiceManager {
return inputManager;
}
public PowerManager getPowerManager() {
public static PowerManager getPowerManager() {
if (powerManager == null) {
powerManager = new PowerManager(getService("power", "android.os.IPowerManager"));
}
return powerManager;
}
public StatusBarManager getStatusBarManager() {
public static StatusBarManager getStatusBarManager() {
if (statusBarManager == null) {
statusBarManager = new StatusBarManager(getService("statusbar", "com.android.internal.statusbar.IStatusBarService"));
}
return statusBarManager;
}
public ClipboardManager getClipboardManager() {
public static ClipboardManager getClipboardManager() {
if (clipboardManager == null) {
IInterface clipboard = getService("clipboard", "android.content.IClipboard");
if (clipboard == null) {
@@ -96,7 +106,7 @@ public final class ServiceManager {
return clipboardManager;
}
public ActivityManager getActivityManager() {
public static ActivityManager getActivityManager() {
if (activityManager == null) {
try {
// On old Android versions, the ActivityManager is not exposed via AIDL,

View File

@@ -0,0 +1,242 @@
package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.DisplayManager;
import android.view.Display;
import org.junit.Assert;
import org.junit.Test;
public class CommandParserTest {
@Test
public void testParseDisplayInfoFromDumpsysDisplay() {
/* @formatter:off */
String partialOutput = "Logical Displays: size=1\n"
+ " Display 0:\n"
+ "mDisplayId=0\n"
+ " mLayerStack=0\n"
+ " mHasContent=true\n"
+ " mDesiredDisplayModeSpecs={baseModeId=2 primaryRefreshRateRange=[90 90] appRequestRefreshRateRange=[90 90]}\n"
+ " mRequestedColorMode=0\n"
+ " mDisplayOffset=(0, 0)\n"
+ " mDisplayScalingDisabled=false\n"
+ " mPrimaryDisplayDevice=Built-in Screen\n"
+ " mBaseDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, FLAG_TRUSTED, "
+ "real 1440 x 3120, largest app 1440 x 3120, smallest app 1440 x 3120, appVsyncOff 2000000, presDeadline 11111111, mode 2, "
+ "defaultMode 1, modes [{id=1, width=1440, height=3120, fps=60.0}, {id=2, width=1440, height=3120, fps=90.0}, {id=3, width=1080, "
+ "height=2340, fps=90.0}, {id=4, width=1080, height=2340, fps=60.0}], hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[2, 3, 4], "
+ "mMaxLuminance=540.0, mMaxAverageLuminance=270.1, mMinLuminance=0.2}, minimalPostProcessingSupported false, rotation 0, state OFF, "
+ "type INTERNAL, uniqueId \"local:0\", app 1440 x 3120, density 600 (515.154 x 514.597) dpi, layerStack 0, colorMode 0, "
+ "supportedColorModes [0, 7, 9], address {port=129, model=0}, deviceProductInfo DeviceProductInfo{name=, manufacturerPnpId=QCM, "
+ "productId=1, modelYear=null, manufactureDate=ManufactureDate{week=27, year=2006}, relativeAddress=null}, removeMode 0}\n"
+ " mOverrideDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, "
+ "FLAG_TRUSTED, real 1440 x 3120, largest app 3120 x 2983, smallest app 1440 x 1303, appVsyncOff 2000000, presDeadline 11111111, "
+ "mode 2, defaultMode 1, modes [{id=1, width=1440, height=3120, fps=60.0}, {id=2, width=1440, height=3120, fps=90.0}, {id=3, "
+ "width=1080, height=2340, fps=90.0}, {id=4, width=1080, height=2340, fps=60.0}], hdrCapabilities "
+ "HdrCapabilities{mSupportedHdrTypes=[2, 3, 4], mMaxLuminance=540.0, mMaxAverageLuminance=270.1, mMinLuminance=0.2}, "
+ "minimalPostProcessingSupported false, rotation 0, state ON, type INTERNAL, uniqueId \"local:0\", app 1440 x 3120, density 600 "
+ "(515.154 x 514.597) dpi, layerStack 0, colorMode 0, supportedColorModes [0, 7, 9], address {port=129, model=0}, deviceProductInfo "
+ "DeviceProductInfo{name=, manufacturerPnpId=QCM, productId=1, modelYear=null, manufactureDate=ManufactureDate{week=27, year=2006}, "
+ "relativeAddress=null}, removeMode 0}\n"
+ " mRequestedMinimalPostProcessing=false\n";
DisplayInfo displayInfo = DisplayManager.parseDisplayInfo(partialOutput, 0);
Assert.assertNotNull(displayInfo);
Assert.assertEquals(0, displayInfo.getDisplayId());
Assert.assertEquals(0, displayInfo.getRotation());
Assert.assertEquals(0, displayInfo.getLayerStack());
// FLAG_TRUSTED does not exist in Display (@TestApi), so it won't be reported
Assert.assertEquals(Display.FLAG_SECURE | Display.FLAG_SUPPORTS_PROTECTED_BUFFERS, displayInfo.getFlags());
Assert.assertEquals(1440, displayInfo.getSize().getWidth());
Assert.assertEquals(3120, displayInfo.getSize().getHeight());
}
@Test
public void testParseDisplayInfoFromDumpsysDisplayWithRotation() {
/* @formatter:off */
String partialOutput = "Logical Displays: size=1\n"
+ " Display 0:\n"
+ "mDisplayId=0\n"
+ " mLayerStack=0\n"
+ " mHasContent=true\n"
+ " mDesiredDisplayModeSpecs={baseModeId=2 primaryRefreshRateRange=[90 90] appRequestRefreshRateRange=[90 90]}\n"
+ " mRequestedColorMode=0\n"
+ " mDisplayOffset=(0, 0)\n"
+ " mDisplayScalingDisabled=false\n"
+ " mPrimaryDisplayDevice=Built-in Screen\n"
+ " mBaseDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, FLAG_TRUSTED, "
+ "real 1440 x 3120, largest app 1440 x 3120, smallest app 1440 x 3120, appVsyncOff 2000000, presDeadline 11111111, mode 2, "
+ "defaultMode 1, modes [{id=1, width=1440, height=3120, fps=60.0}, {id=2, width=1440, height=3120, fps=90.0}, {id=3, width=1080, "
+ "height=2340, fps=90.0}, {id=4, width=1080, height=2340, fps=60.0}], hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[2, 3, 4], "
+ "mMaxLuminance=540.0, mMaxAverageLuminance=270.1, mMinLuminance=0.2}, minimalPostProcessingSupported false, rotation 0, state ON, "
+ "type INTERNAL, uniqueId \"local:0\", app 1440 x 3120, density 600 (515.154 x 514.597) dpi, layerStack 0, colorMode 0, "
+ "supportedColorModes [0, 7, 9], address {port=129, model=0}, deviceProductInfo DeviceProductInfo{name=, manufacturerPnpId=QCM, "
+ "productId=1, modelYear=null, manufactureDate=ManufactureDate{week=27, year=2006}, relativeAddress=null}, removeMode 0}\n"
+ " mOverrideDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, "
+ "FLAG_TRUSTED, real 3120 x 1440, largest app 3120 x 2983, smallest app 1440 x 1303, appVsyncOff 2000000, presDeadline 11111111, "
+ "mode 2, defaultMode 1, modes [{id=1, width=1440, height=3120, fps=60.0}, {id=2, width=1440, height=3120, fps=90.0}, {id=3, "
+ "width=1080, height=2340, fps=90.0}, {id=4, width=1080, height=2340, fps=60.0}], hdrCapabilities "
+ "HdrCapabilities{mSupportedHdrTypes=[2, 3, 4], mMaxLuminance=540.0, mMaxAverageLuminance=270.1, mMinLuminance=0.2}, "
+ "minimalPostProcessingSupported false, rotation 3, state ON, type INTERNAL, uniqueId \"local:0\", app 3120 x 1440, density 600 "
+ "(515.154 x 514.597) dpi, layerStack 0, colorMode 0, supportedColorModes [0, 7, 9], address {port=129, model=0}, deviceProductInfo "
+ "DeviceProductInfo{name=, manufacturerPnpId=QCM, productId=1, modelYear=null, manufactureDate=ManufactureDate{week=27, year=2006}, "
+ "relativeAddress=null}, removeMode 0}\n"
+ " mRequestedMinimalPostProcessing=false";
DisplayInfo displayInfo = DisplayManager.parseDisplayInfo(partialOutput, 0);
Assert.assertNotNull(displayInfo);
Assert.assertEquals(0, displayInfo.getDisplayId());
Assert.assertEquals(3, displayInfo.getRotation());
Assert.assertEquals(0, displayInfo.getLayerStack());
// FLAG_TRUSTED does not exist in Display (@TestApi), so it won't be reported
Assert.assertEquals(Display.FLAG_SECURE | Display.FLAG_SUPPORTS_PROTECTED_BUFFERS, displayInfo.getFlags());
Assert.assertEquals(3120, displayInfo.getSize().getWidth());
Assert.assertEquals(1440, displayInfo.getSize().getHeight());
}
@Test
public void testParseDisplayInfoFromDumpsysDisplayAPI31() {
/* @formatter:off */
String partialOutput = "Logical Displays: size=1\n"
+ " Display 0:\n"
+ " mDisplayId=0\n"
+ " mPhase=1\n"
+ " mLayerStack=0\n"
+ " mHasContent=true\n"
+ " mDesiredDisplayModeSpecs={baseModeId=1 allowGroupSwitching=false primaryRefreshRateRange=[0 60] appRequestRefreshRateRange=[0 "
+ "Infinity]}\n"
+ " mRequestedColorMode=0\n"
+ " mDisplayOffset=(0, 0)\n"
+ " mDisplayScalingDisabled=false\n"
+ " mPrimaryDisplayDevice=Built-in Screen\n"
+ " mBaseDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0\", displayGroupId 0, FLAG_SECURE, "
+ "FLAG_SUPPORTS_PROTECTED_BUFFERS, FLAG_TRUSTED, real 1080 x 2280, largest app 1080 x 2280, smallest app 1080 x 2280, appVsyncOff "
+ "1000000, presDeadline 16666666, mode 1, defaultMode 1, modes [{id=1, width=1080, height=2280, fps=60.000004, "
+ "alternativeRefreshRates=[]}], hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[], mMaxLuminance=500.0, "
+ "mMaxAverageLuminance=500.0, mMinLuminance=0.0}, userDisabledHdrTypes [], minimalPostProcessingSupported false, rotation 0, state "
+ "ON, type INTERNAL, uniqueId \"local:0\", app 1080 x 2280, density 440 (440.0 x 440.0) dpi, layerStack 0, colorMode 0, "
+ "supportedColorModes [0], address {port=0, model=0}, deviceProductInfo DeviceProductInfo{name=EMU_display_0, "
+ "manufacturerPnpId=GGL, productId=1, modelYear=null, manufactureDate=ManufactureDate{week=27, year=2006}, connectionToSinkType=0}, "
+ "removeMode 0, refreshRateOverride 0.0, brightnessMinimum 0.0, brightnessMaximum 1.0, brightnessDefault 0.39763778}\n"
+ " mOverrideDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0\", displayGroupId 0, FLAG_SECURE, "
+ "FLAG_SUPPORTS_PROTECTED_BUFFERS, FLAG_TRUSTED, real 1080 x 2280, largest app 2148 x 2065, smallest app 1080 x 997, appVsyncOff "
+ "1000000, presDeadline 16666666, mode 1, defaultMode 1, modes [{id=1, width=1080, height=2280, fps=60.000004, "
+ "alternativeRefreshRates=[]}], hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[], mMaxLuminance=500.0, "
+ "mMaxAverageLuminance=500.0, mMinLuminance=0.0}, userDisabledHdrTypes [], minimalPostProcessingSupported false, rotation 0, state "
+ "ON, type INTERNAL, uniqueId \"local:0\", app 1080 x 2148, density 440 (440.0 x 440.0) dpi, layerStack 0, colorMode 0, "
+ "supportedColorModes [0], address {port=0, model=0}, deviceProductInfo DeviceProductInfo{name=EMU_display_0, "
+ "manufacturerPnpId=GGL, productId=1, modelYear=null, manufactureDate=ManufactureDate{week=27, year=2006}, connectionToSinkType=0}, "
+ "removeMode 0, refreshRateOverride 0.0, brightnessMinimum 0.0, brightnessMaximum 1.0, brightnessDefault 0.39763778}\n"
+ " mRequestedMinimalPostProcessing=false\n"
+ " mFrameRateOverrides=[]\n"
+ " mPendingFrameRateOverrideUids={}\n";
DisplayInfo displayInfo = DisplayManager.parseDisplayInfo(partialOutput, 0);
Assert.assertNotNull(displayInfo);
Assert.assertEquals(0, displayInfo.getDisplayId());
Assert.assertEquals(0, displayInfo.getRotation());
Assert.assertEquals(0, displayInfo.getLayerStack());
// FLAG_TRUSTED does not exist in Display (@TestApi), so it won't be reported
Assert.assertEquals(Display.FLAG_SECURE | Display.FLAG_SUPPORTS_PROTECTED_BUFFERS, displayInfo.getFlags());
Assert.assertEquals(1080, displayInfo.getSize().getWidth());
Assert.assertEquals(2280, displayInfo.getSize().getHeight());
}
@Test
public void testParseDisplayInfoFromDumpsysDisplayAPI31NoFlags() {
/* @formatter:off */
String partialOutput = "Logical Displays: size=1\n"
+ " Display 0:\n"
+ " mDisplayId=0\n"
+ " mPhase=1\n"
+ " mLayerStack=0\n"
+ " mHasContent=true\n"
+ " mDesiredDisplayModeSpecs={baseModeId=1 allowGroupSwitching=false primaryRefreshRateRange=[0 60] appRequestRefreshRateRange=[0 "
+ "Infinity]}\n"
+ " mRequestedColorMode=0\n"
+ " mDisplayOffset=(0, 0)\n"
+ " mDisplayScalingDisabled=false\n"
+ " mPrimaryDisplayDevice=Built-in Screen\n"
+ " mBaseDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0\", displayGroupId 0, "
+ "real 1080 x 2280, largest app 1080 x 2280, smallest app 1080 x 2280, appVsyncOff "
+ "1000000, presDeadline 16666666, mode 1, defaultMode 1, modes [{id=1, width=1080, height=2280, fps=60.000004, "
+ "alternativeRefreshRates=[]}], hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[], mMaxLuminance=500.0, "
+ "mMaxAverageLuminance=500.0, mMinLuminance=0.0}, userDisabledHdrTypes [], minimalPostProcessingSupported false, rotation 0, state "
+ "ON, type INTERNAL, uniqueId \"local:0\", app 1080 x 2280, density 440 (440.0 x 440.0) dpi, layerStack 0, colorMode 0, "
+ "supportedColorModes [0], address {port=0, model=0}, deviceProductInfo DeviceProductInfo{name=EMU_display_0, "
+ "manufacturerPnpId=GGL, productId=1, modelYear=null, manufactureDate=ManufactureDate{week=27, year=2006}, connectionToSinkType=0}, "
+ "removeMode 0, refreshRateOverride 0.0, brightnessMinimum 0.0, brightnessMaximum 1.0, brightnessDefault 0.39763778}\n"
+ " mOverrideDisplayInfo=DisplayInfo{\"Built-in Screen\", displayId 0\", displayGroupId 0, "
+ "real 1080 x 2280, largest app 2148 x 2065, smallest app 1080 x 997, appVsyncOff "
+ "1000000, presDeadline 16666666, mode 1, defaultMode 1, modes [{id=1, width=1080, height=2280, fps=60.000004, "
+ "alternativeRefreshRates=[]}], hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[], mMaxLuminance=500.0, "
+ "mMaxAverageLuminance=500.0, mMinLuminance=0.0}, userDisabledHdrTypes [], minimalPostProcessingSupported false, rotation 0, state "
+ "ON, type INTERNAL, uniqueId \"local:0\", app 1080 x 2148, density 440 (440.0 x 440.0) dpi, layerStack 0, colorMode 0, "
+ "supportedColorModes [0], address {port=0, model=0}, deviceProductInfo DeviceProductInfo{name=EMU_display_0, "
+ "manufacturerPnpId=GGL, productId=1, modelYear=null, manufactureDate=ManufactureDate{week=27, year=2006}, connectionToSinkType=0}, "
+ "removeMode 0, refreshRateOverride 0.0, brightnessMinimum 0.0, brightnessMaximum 1.0, brightnessDefault 0.39763778}\n"
+ " mRequestedMinimalPostProcessing=false\n"
+ " mFrameRateOverrides=[]\n"
+ " mPendingFrameRateOverrideUids={}\n";
DisplayInfo displayInfo = DisplayManager.parseDisplayInfo(partialOutput, 0);
Assert.assertNotNull(displayInfo);
Assert.assertEquals(0, displayInfo.getDisplayId());
Assert.assertEquals(0, displayInfo.getRotation());
Assert.assertEquals(0, displayInfo.getLayerStack());
Assert.assertEquals(0, displayInfo.getFlags());
Assert.assertEquals(1080, displayInfo.getSize().getWidth());
Assert.assertEquals(2280, displayInfo.getSize().getHeight());
}
@Test
public void testParseDisplayInfoFromDumpsysDisplayAPI29WithNoFlags() {
/* @formatter:off */
String partialOutput = "Logical Displays: size=2\n"
+ " Display 0:\n"
+ " mDisplayId=0\n"
+ " mLayerStack=0\n"
+ " mHasContent=true\n"
+ " mAllowedDisplayModes=[1]\n"
+ " mRequestedColorMode=0\n"
+ " mDisplayOffset=(0, 0)\n"
+ " mDisplayScalingDisabled=false\n"
+ " mPrimaryDisplayDevice=Built-in Screen\n"
+ " mBaseDisplayInfo=DisplayInfo{\"Built-in Screen, displayId 0\", uniqueId \"local:0\", app 3664 x 1920, "
+ "real 3664 x 1920, largest app 3664 x 1920, smallest app 3664 x 1920, mode 61, defaultMode 61, modes ["
+ "{id=1, width=3664, height=1920, fps=60.000004}, {id=2, width=3664, height=1920, fps=61.000004}, "
+ "{id=61, width=3664, height=1920, fps=120.00001}], colorMode 0, supportedColorModes [0], "
+ "hdrCapabilities android.view.Display$HdrCapabilities@4a41fe79, rotation 0, density 290 (320.842 x 319.813) dpi, "
+ "layerStack 0, appVsyncOff 1000000, presDeadline 8333333, type BUILT_IN, address {port=129, model=0}, "
+ "state ON, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, removeMode 0}\n"
+ " mOverrideDisplayInfo=DisplayInfo{\"Built-in Screen, displayId 0\", uniqueId \"local:0\", app 3664 x 1920, "
+ "real 3664 x 1920, largest app 3664 x 3620, smallest app 1920 x 1876, mode 61, defaultMode 61, modes ["
+ "{id=1, width=3664, height=1920, fps=60.000004}, {id=2, width=3664, height=1920, fps=61.000004}, "
+ "{id=61, width=3664, height=1920, fps=120.00001}], colorMode 0, supportedColorModes [0], "
+ "hdrCapabilities android.view.Display$HdrCapabilities@4a41fe79, rotation 0, density 290 (320.842 x 319.813) dpi, "
+ "layerStack 0, appVsyncOff 1000000, presDeadline 8333333, type BUILT_IN, address {port=129, model=0}, "
+ "state ON, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, removeMode 0}\n"
+ " Display 31:\n"
+ " mDisplayId=31\n"
+ " mLayerStack=31\n"
+ " mHasContent=true\n"
+ " mAllowedDisplayModes=[92]\n"
+ " mRequestedColorMode=0\n"
+ " mDisplayOffset=(0, 0)\n"
+ " mDisplayScalingDisabled=false\n"
+ " mPrimaryDisplayDevice=PanelLayer-#main\n"
+ " mBaseDisplayInfo=DisplayInfo{\"PanelLayer-#main, displayId 31\", uniqueId "
+ "\"virtual:com.test.system,10040,PanelLayer-#main,0\", app 800 x 110, real 800 x 110, largest app 800 x 110, smallest app 800 x "
+ "110, mode 92, defaultMode 92, modes [{id=92, width=800, height=110, fps=60.0}], colorMode 0, supportedColorModes [0], "
+ "hdrCapabilities null, rotation 0, density 200 (200.0 x 200.0) dpi, layerStack 31, appVsyncOff 0, presDeadline 16666666, "
+ "type VIRTUAL, state ON, owner com.test.system (uid 10040), FLAG_PRIVATE, removeMode 1}\n"
+ " mOverrideDisplayInfo=DisplayInfo{\"PanelLayer-#main, displayId 31\", uniqueId "
+ "\"virtual:com.test.system,10040,PanelLayer-#main,0\", app 800 x 110, real 800 x 110, largest app 800 x 800, smallest app 110 x "
+ "110, mode 92, defaultMode 92, modes [{id=92, width=800, height=110, fps=60.0}], colorMode 0, supportedColorModes [0], "
+ "hdrCapabilities null, rotation 0, density 200 (200.0 x 200.0) dpi, layerStack 31, appVsyncOff 0, presDeadline 16666666, "
+ "type VIRTUAL, state OFF, owner com.test.system (uid 10040), FLAG_PRIVATE, removeMode 1}\n";
DisplayInfo displayInfo = DisplayManager.parseDisplayInfo(partialOutput, 31);
Assert.assertNotNull(displayInfo);
Assert.assertEquals(31, displayInfo.getDisplayId());
Assert.assertEquals(0, displayInfo.getRotation());
Assert.assertEquals(31, displayInfo.getLayerStack());
Assert.assertEquals(0, displayInfo.getFlags());
Assert.assertEquals(800, displayInfo.getSize().getWidth());
Assert.assertEquals(110, displayInfo.getSize().getHeight());
}
}