Compare commits
30 Commits
meson_sour
...
install
Author | SHA1 | Date | |
---|---|---|---|
b4e714f77d | |||
c0e00c485b | |||
8578c5072e | |||
d459a2052f | |||
ab945efe28 | |||
2ac7384797 | |||
06bc5da24a | |||
a5d3b5b827 | |||
174dc17a80 | |||
56eb9c3106 | |||
40644994e8 | |||
7505f7117e | |||
949b64dff2 | |||
00e9e69c2a | |||
4a5cdcd390 | |||
e5e210506f | |||
a2a22f497f | |||
51a1762cbd | |||
c1ec1d1023 | |||
0a0a446ea6 | |||
fccfc43b9e | |||
121bb71dfe | |||
57056d078d | |||
1f138aef41 | |||
1ab6c19486 | |||
fd3483c837 | |||
041cdf6cf5 | |||
136ab8c199 | |||
3848ce86f1 | |||
5b8e9aa0e9 |
13
app/data/scrcpy-console.desktop
Normal file
13
app/data/scrcpy-console.desktop
Normal 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
13
app/data/scrcpy.desktop
Normal 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
|
@ -188,7 +188,7 @@ conf.set_quoted('SCRCPY_VERSION', meson.project_version())
|
|||||||
# the prefix used during configuration (meson --prefix=PREFIX)
|
# the prefix used during configuration (meson --prefix=PREFIX)
|
||||||
conf.set_quoted('PREFIX', get_option('prefix'))
|
conf.set_quoted('PREFIX', get_option('prefix'))
|
||||||
|
|
||||||
# build a "portable" version (with scrcpy-server accessible from the same
|
# build a "portable" version (with scrcpy-server.apk accessible from the same
|
||||||
# directory as the executable)
|
# directory as the executable)
|
||||||
conf.set('PORTABLE', get_option('portable'))
|
conf.set('PORTABLE', get_option('portable'))
|
||||||
|
|
||||||
@ -223,14 +223,26 @@ executable('scrcpy', src,
|
|||||||
install: true,
|
install: true,
|
||||||
c_args: [])
|
c_args: [])
|
||||||
|
|
||||||
|
# <https://mesonbuild.com/Builtin-options.html#directories>
|
||||||
|
datadir = get_option('datadir') # by default 'share'
|
||||||
|
|
||||||
install_man('scrcpy.1')
|
install_man('scrcpy.1')
|
||||||
install_data('data/icon.png',
|
install_data('data/icon.png',
|
||||||
rename: 'scrcpy.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_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_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
|
### TESTS
|
||||||
@ -245,8 +257,8 @@ if get_option('buildtype') == 'debug'
|
|||||||
'src/util/str.c',
|
'src/util/str.c',
|
||||||
'src/util/strbuf.c',
|
'src/util/strbuf.c',
|
||||||
]],
|
]],
|
||||||
['test_buffer_util', [
|
['test_binary', [
|
||||||
'tests/test_buffer_util.c',
|
'tests/test_binary.c',
|
||||||
]],
|
]],
|
||||||
['test_cbuf', [
|
['test_cbuf', [
|
||||||
'tests/test_cbuf.c',
|
'tests/test_cbuf.c',
|
||||||
|
@ -110,6 +110,10 @@ However, the option is only available when the HID keyboard is enabled (or a phy
|
|||||||
|
|
||||||
Also see \fB\-\-hid\-mouse\fR.
|
Also see \fB\-\-hid\-mouse\fR.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-\-install
|
||||||
|
Install the server (via "adb install") rather than pushing it to /data/local/tmp (via "adb push").
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-\-legacy\-paste
|
.B \-\-legacy\-paste
|
||||||
Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+Shift+v).
|
Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+Shift+v).
|
||||||
@ -242,6 +246,10 @@ option if set, or by the file extension (.mp4 or .mkv).
|
|||||||
.BI "\-\-record\-format " format
|
.BI "\-\-record\-format " format
|
||||||
Force recording format (either mp4 or mkv).
|
Force recording format (either mp4 or mkv).
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-\-reinstall
|
||||||
|
Reinstall the server (via "adb install"), even if the correct version is already installed. Implies \fB\-\-install\fR.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-render\-driver " name
|
.BI "\-\-render\-driver " name
|
||||||
Request SDL to use the given render driver (this is just a hint).
|
Request SDL to use the given render driver (this is just a hint).
|
||||||
|
@ -329,6 +329,17 @@ sc_adb_install(struct sc_intr *intr, const char *serial, const char *local,
|
|||||||
return process_check_success_intr(intr, pid, "adb install", flags);
|
return process_check_success_intr(intr, pid, "adb install", flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_adb_uninstall(struct sc_intr *intr, const char *serial, const char *pkg,
|
||||||
|
unsigned flags) {
|
||||||
|
assert(serial);
|
||||||
|
const char *const argv[] =
|
||||||
|
SC_ADB_COMMAND("-s", serial, "uninstall", pkg);
|
||||||
|
|
||||||
|
sc_pid pid = sc_adb_execute(argv, flags);
|
||||||
|
return process_check_success_intr(intr, pid, "adb uninstall", flags);
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_adb_tcpip(struct sc_intr *intr, const char *serial, uint16_t port,
|
sc_adb_tcpip(struct sc_intr *intr, const char *serial, uint16_t port,
|
||||||
unsigned flags) {
|
unsigned flags) {
|
||||||
@ -435,6 +446,7 @@ sc_adb_list_devices(struct sc_intr *intr, unsigned flags,
|
|||||||
"Please report an issue.");
|
"Please report an issue.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
#undef BUFSIZE
|
||||||
|
|
||||||
// It is parsed as a NUL-terminated string
|
// It is parsed as a NUL-terminated string
|
||||||
buf[r] = '\0';
|
buf[r] = '\0';
|
||||||
@ -713,3 +725,99 @@ sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) {
|
|||||||
|
|
||||||
return sc_adb_parse_device_ip(buf);
|
return sc_adb_parse_device_ip(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
sc_adb_get_installed_apk_path(struct sc_intr *intr, const char *serial,
|
||||||
|
unsigned flags) {
|
||||||
|
assert(serial);
|
||||||
|
const char *const argv[] =
|
||||||
|
SC_ADB_COMMAND("-s", serial, "shell", "pm", "list", "package", "-f",
|
||||||
|
SC_ANDROID_PACKAGE);
|
||||||
|
|
||||||
|
sc_pipe pout;
|
||||||
|
sc_pid pid = sc_adb_execute_p(argv, flags, &pout);
|
||||||
|
if (pid == SC_PROCESS_NONE) {
|
||||||
|
LOGD("Could not execute \"pm list packages\"");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// "pm list packages -f <package>" output should contain only one line, so
|
||||||
|
// the output should be short
|
||||||
|
char buf[1024];
|
||||||
|
ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf) - 1);
|
||||||
|
sc_pipe_close(pout);
|
||||||
|
|
||||||
|
bool ok = process_check_success_intr(intr, pid, "pm list packages", flags);
|
||||||
|
if (!ok) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r == -1) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert((size_t) r < sizeof(buf));
|
||||||
|
if (r == sizeof(buf) - 1) {
|
||||||
|
// The implementation assumes that the output of "pm list packages"
|
||||||
|
// fits in the buffer in a single pass
|
||||||
|
LOGW("Result of \"pm list package\" does not fit in 1Kb. "
|
||||||
|
"Please report an issue.");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// It is parsed as a NUL-terminated string
|
||||||
|
buf[r] = '\0';
|
||||||
|
|
||||||
|
return sc_adb_parse_installed_apk_path(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
sc_adb_get_installed_apk_version(struct sc_intr *intr, const char *serial,
|
||||||
|
unsigned flags) {
|
||||||
|
assert(serial);
|
||||||
|
const char *const argv[] =
|
||||||
|
SC_ADB_COMMAND("-s", serial, "shell", "dumpsys", "package",
|
||||||
|
SC_ANDROID_PACKAGE);
|
||||||
|
|
||||||
|
sc_pipe pout;
|
||||||
|
sc_pid pid = sc_adb_execute_p(argv, flags, &pout);
|
||||||
|
if (pid == SC_PROCESS_NONE) {
|
||||||
|
LOGD("Could not execute \"dumpsys package\"");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// "dumpsys package" output can be huge (e.g. 16k), but versionName is at
|
||||||
|
// the beginning, typically in the first 1024 bytes (64k should be enough
|
||||||
|
// for the whole output anyway)
|
||||||
|
#define BUFSIZE 65536
|
||||||
|
char *buf = malloc(BUFSIZE);
|
||||||
|
if (!buf) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, BUFSIZE - 1);
|
||||||
|
sc_pipe_close(pout);
|
||||||
|
|
||||||
|
bool ok = process_check_success_intr(intr, pid, "dumpsys package", flags);
|
||||||
|
if (!ok) {
|
||||||
|
free(buf);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r == -1) {
|
||||||
|
free(buf);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert((size_t) r < BUFSIZE);
|
||||||
|
#undef BUFSIZE
|
||||||
|
// if r == sizeof(buf), then the output is truncated, but we don't care,
|
||||||
|
// versionName is at the beginning in practice, and is unlikely to be
|
||||||
|
// truncated at 64k
|
||||||
|
|
||||||
|
// It is parsed as a NUL-terminated string
|
||||||
|
buf[r] = '\0';
|
||||||
|
|
||||||
|
char *version = sc_adb_parse_installed_apk_version(buf);
|
||||||
|
free(buf);
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
|
|
||||||
#define SC_ADB_SILENT (SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR)
|
#define SC_ADB_SILENT (SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR)
|
||||||
|
|
||||||
|
#define SC_ANDROID_PACKAGE "com.genymobile.scrcpy"
|
||||||
|
|
||||||
const char *
|
const char *
|
||||||
sc_adb_get_executable(void);
|
sc_adb_get_executable(void);
|
||||||
|
|
||||||
@ -64,6 +66,10 @@ bool
|
|||||||
sc_adb_install(struct sc_intr *intr, const char *serial, const char *local,
|
sc_adb_install(struct sc_intr *intr, const char *serial, const char *local,
|
||||||
unsigned flags);
|
unsigned flags);
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_adb_uninstall(struct sc_intr *intr, const char *serial, const char *pkg,
|
||||||
|
unsigned flags);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute `adb tcpip <port>`
|
* Execute `adb tcpip <port>`
|
||||||
*/
|
*/
|
||||||
@ -114,4 +120,18 @@ sc_adb_getprop(struct sc_intr *intr, const char *serial, const char *prop,
|
|||||||
char *
|
char *
|
||||||
sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags);
|
sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the path of the installed APK for com.genymobile.scrcpy (if any)
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
sc_adb_get_installed_apk_path(struct sc_intr *intr, const char *serial,
|
||||||
|
unsigned flags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the version of the installed APK for com.genymobile.scrcpy (if any)
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
sc_adb_get_installed_apk_version(struct sc_intr *intr, const char *serial,
|
||||||
|
unsigned flags);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -225,3 +225,62 @@ sc_adb_parse_device_ip(char *str) {
|
|||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
sc_adb_parse_installed_apk_path(char *str) {
|
||||||
|
// str is expected to look like:
|
||||||
|
// "package:/data/app/.../base.apk=com.genymobile.scrcpy"
|
||||||
|
// ^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
// We want to extract the path (which may contain '=', even in practice)
|
||||||
|
|
||||||
|
if (strncmp(str, "package:", 8)) {
|
||||||
|
// Does not start with "package:"
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *s = str + 8;
|
||||||
|
size_t len = strcspn(s, " \r\n");
|
||||||
|
s[len] = '\0';
|
||||||
|
|
||||||
|
char *p = strrchr(s, '=');
|
||||||
|
if (!p) {
|
||||||
|
// No '=' found
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Truncate at the last '='
|
||||||
|
*p = '\0';
|
||||||
|
|
||||||
|
return strdup(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
sc_adb_parse_installed_apk_version(const char *str) {
|
||||||
|
// str is the (beginning of the) output of `dumpsys package`
|
||||||
|
// We want to extract the version string from a line starting with 4 spaces
|
||||||
|
// then `versionName=` then the version string.
|
||||||
|
|
||||||
|
#define VERSION_NAME_PREFIX "\n versionName="
|
||||||
|
char *s = strstr(str, VERSION_NAME_PREFIX);
|
||||||
|
if (!s) {
|
||||||
|
// Not found
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
s+= sizeof(VERSION_NAME_PREFIX) - 1;
|
||||||
|
|
||||||
|
size_t len = strspn(s, "0123456789.");
|
||||||
|
if (!len) {
|
||||||
|
LOGW("Unexpected version name with no value");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *version = malloc(len + 1);
|
||||||
|
if (!version) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(version, s, len);
|
||||||
|
version[len] = '\0';
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
@ -27,4 +27,24 @@ sc_adb_parse_devices(char *str, struct sc_vec_adb_devices *out_vec);
|
|||||||
char *
|
char *
|
||||||
sc_adb_parse_device_ip(char *str);
|
sc_adb_parse_device_ip(char *str);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the package path from the output of
|
||||||
|
* `adb shell pm list packages -f <package>`
|
||||||
|
*
|
||||||
|
* The parameter must be a NUL-terminated string.
|
||||||
|
*
|
||||||
|
* Warning: this function modifies the buffer for optimization purposes.
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
sc_adb_parse_installed_apk_path(char *str);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the package version from the output of
|
||||||
|
* `adb shell dumpsys package <package>`
|
||||||
|
*
|
||||||
|
* The parameter must be a NUL-terminated string.
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
sc_adb_parse_installed_apk_version(const char *str);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -57,6 +57,8 @@
|
|||||||
#define OPT_NO_CLEANUP 1037
|
#define OPT_NO_CLEANUP 1037
|
||||||
#define OPT_PRINT_FPS 1038
|
#define OPT_PRINT_FPS 1038
|
||||||
#define OPT_NO_POWER_ON 1039
|
#define OPT_NO_POWER_ON 1039
|
||||||
|
#define OPT_INSTALL 1040
|
||||||
|
#define OPT_REINSTALL 1041
|
||||||
|
|
||||||
struct sc_option {
|
struct sc_option {
|
||||||
char shortopt;
|
char shortopt;
|
||||||
@ -207,6 +209,12 @@ static const struct sc_option options[] = {
|
|||||||
.longopt = "help",
|
.longopt = "help",
|
||||||
.text = "Print this help.",
|
.text = "Print this help.",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.longopt_id = OPT_INSTALL,
|
||||||
|
.longopt = "install",
|
||||||
|
.text = "Install the server (via 'adb install') rather than pushing "
|
||||||
|
"it to /data/local/tmp (via 'adb push').",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_LEGACY_PASTE,
|
.longopt_id = OPT_LEGACY_PASTE,
|
||||||
.longopt = "legacy-paste",
|
.longopt = "legacy-paste",
|
||||||
@ -378,6 +386,13 @@ static const struct sc_option options[] = {
|
|||||||
.argdesc = "format",
|
.argdesc = "format",
|
||||||
.text = "Force recording format (either mp4 or mkv).",
|
.text = "Force recording format (either mp4 or mkv).",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.longopt_id = OPT_REINSTALL,
|
||||||
|
.longopt = "reinstall",
|
||||||
|
.text = "Reinstall the server (via 'adb install'), even if the correct "
|
||||||
|
"version is already installed.\n"
|
||||||
|
"Implies --install.",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_RENDER_DRIVER,
|
.longopt_id = OPT_RENDER_DRIVER,
|
||||||
.longopt = "render-driver",
|
.longopt = "render-driver",
|
||||||
@ -1610,6 +1625,13 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
case OPT_PRINT_FPS:
|
case OPT_PRINT_FPS:
|
||||||
opts->start_fps_counter = true;
|
opts->start_fps_counter = true;
|
||||||
break;
|
break;
|
||||||
|
case OPT_INSTALL:
|
||||||
|
opts->install = true;
|
||||||
|
break;
|
||||||
|
case OPT_REINSTALL:
|
||||||
|
opts->install = true;
|
||||||
|
opts->reinstall = true;
|
||||||
|
break;
|
||||||
case OPT_OTG:
|
case OPT_OTG:
|
||||||
#ifdef HAVE_USB
|
#ifdef HAVE_USB
|
||||||
opts->otg = true;
|
opts->otg = true;
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "util/buffer_util.h"
|
#include "util/binary.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/str.h"
|
#include "util/str.h"
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ static const char *const android_motionevent_action_labels[] = {
|
|||||||
"move",
|
"move",
|
||||||
"cancel",
|
"cancel",
|
||||||
"outside",
|
"outside",
|
||||||
"ponter-down",
|
"pointer-down",
|
||||||
"pointer-up",
|
"pointer-up",
|
||||||
"hover-move",
|
"hover-move",
|
||||||
"scroll",
|
"scroll",
|
||||||
@ -78,16 +78,6 @@ write_string(const char *utf8, size_t max_len, unsigned char *buf) {
|
|||||||
return 4 + len;
|
return 4 + len;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint16_t
|
|
||||||
to_fixed_point_16(float f) {
|
|
||||||
assert(f >= 0.0f && f <= 1.0f);
|
|
||||||
uint32_t u = f * 0x1p16f; // 2^16
|
|
||||||
if (u >= 0xffff) {
|
|
||||||
u = 0xffff;
|
|
||||||
}
|
|
||||||
return (uint16_t) u;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) {
|
sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) {
|
||||||
buf[0] = msg->type;
|
buf[0] = msg->type;
|
||||||
@ -109,18 +99,20 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) {
|
|||||||
sc_write64be(&buf[2], msg->inject_touch_event.pointer_id);
|
sc_write64be(&buf[2], msg->inject_touch_event.pointer_id);
|
||||||
write_position(&buf[10], &msg->inject_touch_event.position);
|
write_position(&buf[10], &msg->inject_touch_event.position);
|
||||||
uint16_t pressure =
|
uint16_t pressure =
|
||||||
to_fixed_point_16(msg->inject_touch_event.pressure);
|
sc_float_to_u16fp(msg->inject_touch_event.pressure);
|
||||||
sc_write16be(&buf[22], pressure);
|
sc_write16be(&buf[22], pressure);
|
||||||
sc_write32be(&buf[24], msg->inject_touch_event.buttons);
|
sc_write32be(&buf[24], msg->inject_touch_event.buttons);
|
||||||
return 28;
|
return 28;
|
||||||
case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
|
case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
|
||||||
write_position(&buf[1], &msg->inject_scroll_event.position);
|
write_position(&buf[1], &msg->inject_scroll_event.position);
|
||||||
sc_write32be(&buf[13],
|
int16_t hscroll =
|
||||||
(uint32_t) msg->inject_scroll_event.hscroll);
|
sc_float_to_i16fp(msg->inject_scroll_event.hscroll);
|
||||||
sc_write32be(&buf[17],
|
int16_t vscroll =
|
||||||
(uint32_t) msg->inject_scroll_event.vscroll);
|
sc_float_to_i16fp(msg->inject_scroll_event.vscroll);
|
||||||
sc_write32be(&buf[21], msg->inject_scroll_event.buttons);
|
sc_write16be(&buf[13], (uint16_t) hscroll);
|
||||||
return 25;
|
sc_write16be(&buf[15], (uint16_t) vscroll);
|
||||||
|
sc_write32be(&buf[17], msg->inject_scroll_event.buttons);
|
||||||
|
return 21;
|
||||||
case SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
|
case SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
|
||||||
buf[1] = msg->inject_keycode.action;
|
buf[1] = msg->inject_keycode.action;
|
||||||
return 2;
|
return 2;
|
||||||
@ -191,8 +183,8 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
|
case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
|
||||||
LOG_CMSG("scroll position=%" PRIi32 ",%" PRIi32 " hscroll=%" PRIi32
|
LOG_CMSG("scroll position=%" PRIi32 ",%" PRIi32 " hscroll=%f"
|
||||||
" vscroll=%" PRIi32 " buttons=%06lx",
|
" vscroll=%f buttons=%06lx",
|
||||||
msg->inject_scroll_event.position.point.x,
|
msg->inject_scroll_event.position.point.x,
|
||||||
msg->inject_scroll_event.position.point.y,
|
msg->inject_scroll_event.position.point.y,
|
||||||
msg->inject_scroll_event.hscroll,
|
msg->inject_scroll_event.hscroll,
|
||||||
|
@ -68,8 +68,8 @@ struct sc_control_msg {
|
|||||||
} inject_touch_event;
|
} inject_touch_event;
|
||||||
struct {
|
struct {
|
||||||
struct sc_position position;
|
struct sc_position position;
|
||||||
int32_t hscroll;
|
float hscroll;
|
||||||
int32_t vscroll;
|
float vscroll;
|
||||||
enum android_motionevent_buttons buttons;
|
enum android_motionevent_buttons buttons;
|
||||||
} inject_scroll_event;
|
} inject_scroll_event;
|
||||||
struct {
|
struct {
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
#include "decoder.h"
|
#include "decoder.h"
|
||||||
#include "events.h"
|
#include "events.h"
|
||||||
#include "recorder.h"
|
#include "recorder.h"
|
||||||
#include "util/buffer_util.h"
|
#include "util/binary.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
#define SC_PACKET_HEADER_SIZE 12
|
#define SC_PACKET_HEADER_SIZE 12
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "util/buffer_util.h"
|
#include "util/binary.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
ssize_t
|
ssize_t
|
||||||
|
@ -358,8 +358,8 @@ struct sc_mouse_click_event {
|
|||||||
|
|
||||||
struct sc_mouse_scroll_event {
|
struct sc_mouse_scroll_event {
|
||||||
struct sc_position position;
|
struct sc_position position;
|
||||||
int32_t hscroll;
|
float hscroll;
|
||||||
int32_t vscroll;
|
float vscroll;
|
||||||
uint8_t buttons_state; // bitwise-OR of sc_mouse_button values
|
uint8_t buttons_state; // bitwise-OR of sc_mouse_button values
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -747,8 +747,13 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
|
|||||||
.point = sc_screen_convert_window_to_frame_coords(im->screen,
|
.point = sc_screen_convert_window_to_frame_coords(im->screen,
|
||||||
mouse_x, mouse_y),
|
mouse_x, mouse_y),
|
||||||
},
|
},
|
||||||
.hscroll = event->x,
|
#if SDL_VERSION_ATLEAST(2, 0, 18)
|
||||||
.vscroll = event->y,
|
.hscroll = CLAMP(event->preciseX, -1.0f, 1.0f),
|
||||||
|
.vscroll = CLAMP(event->preciseY, -1.0f, 1.0f),
|
||||||
|
#else
|
||||||
|
.hscroll = CLAMP(event->x, -1, 1),
|
||||||
|
.vscroll = CLAMP(event->y, -1, 1),
|
||||||
|
#endif
|
||||||
.buttons_state =
|
.buttons_state =
|
||||||
sc_mouse_buttons_state_from_sdl(buttons, im->forward_all_clicks),
|
sc_mouse_buttons_state_from_sdl(buttons, im->forward_all_clicks),
|
||||||
};
|
};
|
||||||
|
@ -65,4 +65,6 @@ const struct scrcpy_options scrcpy_options_default = {
|
|||||||
.cleanup = true,
|
.cleanup = true,
|
||||||
.start_fps_counter = false,
|
.start_fps_counter = false,
|
||||||
.power_on = true,
|
.power_on = true,
|
||||||
|
.install = false,
|
||||||
|
.reinstall = false,
|
||||||
};
|
};
|
||||||
|
@ -140,6 +140,8 @@ struct scrcpy_options {
|
|||||||
bool cleanup;
|
bool cleanup;
|
||||||
bool start_fps_counter;
|
bool start_fps_counter;
|
||||||
bool power_on;
|
bool power_on;
|
||||||
|
bool install;
|
||||||
|
bool reinstall;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern const struct scrcpy_options scrcpy_options_default;
|
extern const struct scrcpy_options scrcpy_options_default;
|
||||||
|
@ -325,6 +325,8 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
.tcpip_dst = options->tcpip_dst,
|
.tcpip_dst = options->tcpip_dst,
|
||||||
.cleanup = options->cleanup,
|
.cleanup = options->cleanup,
|
||||||
.power_on = options->power_on,
|
.power_on = options->power_on,
|
||||||
|
.install = options->install,
|
||||||
|
.reinstall = options->reinstall,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct sc_server_callbacks cbs = {
|
static const struct sc_server_callbacks cbs = {
|
||||||
|
@ -14,10 +14,11 @@
|
|||||||
#include "util/process_intr.h"
|
#include "util/process_intr.h"
|
||||||
#include "util/str.h"
|
#include "util/str.h"
|
||||||
|
|
||||||
#define SC_SERVER_FILENAME "scrcpy-server"
|
#define SC_SERVER_FILENAME "scrcpy-server.apk"
|
||||||
|
#define SC_SERVER_PACKAGE "com.genymobile.scrcpy"
|
||||||
|
|
||||||
#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_DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.apk"
|
||||||
|
|
||||||
static char *
|
static char *
|
||||||
get_server_path(void) {
|
get_server_path(void) {
|
||||||
@ -104,7 +105,10 @@ error:
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
push_server(struct sc_intr *intr, const char *serial) {
|
push_server(struct sc_intr *intr, const char *serial, bool install,
|
||||||
|
bool reinstall) {
|
||||||
|
assert(install || !reinstall); // reinstall implies install
|
||||||
|
|
||||||
char *server_path = get_server_path();
|
char *server_path = get_server_path();
|
||||||
if (!server_path) {
|
if (!server_path) {
|
||||||
return false;
|
return false;
|
||||||
@ -114,7 +118,28 @@ 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);
|
bool ok;
|
||||||
|
|
||||||
|
if (install) {
|
||||||
|
char *version = sc_adb_get_installed_apk_version(intr, serial, 0);
|
||||||
|
bool same_version = version && !strcmp(version, SCRCPY_VERSION);
|
||||||
|
free(version);
|
||||||
|
if (!reinstall && same_version) {
|
||||||
|
LOGI("Server " SCRCPY_VERSION " already installed");
|
||||||
|
ok = true;
|
||||||
|
} else {
|
||||||
|
LOGI("Installing server " SCRCPY_VERSION);
|
||||||
|
// If a server with a different signature is installed, or if a
|
||||||
|
// newer server is already installed, we must uninstall it first.
|
||||||
|
ok = sc_adb_uninstall(intr, serial, SC_SERVER_PACKAGE,
|
||||||
|
SC_ADB_SILENT);
|
||||||
|
(void) ok; // expected to fail if it is not installed
|
||||||
|
ok = sc_adb_install(intr, serial, server_path, 0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ok = sc_adb_push(intr, serial, server_path, SC_DEVICE_SERVER_PATH, 0);
|
||||||
|
}
|
||||||
|
|
||||||
free(server_path);
|
free(server_path);
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
@ -152,6 +177,38 @@ sc_server_sleep(struct sc_server *server, sc_tick deadline) {
|
|||||||
return !stopped;
|
return !stopped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
get_classpath_cmd(struct sc_intr *intr, const char *serial, bool install) {
|
||||||
|
if (!install) {
|
||||||
|
// In push mode, the path is known statically
|
||||||
|
char *cp = strdup("CLASSPATH=" SC_DEVICE_SERVER_PATH);
|
||||||
|
if (!cp) {
|
||||||
|
LOG_OOM();
|
||||||
|
}
|
||||||
|
return cp;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *apk_path = sc_adb_get_installed_apk_path(intr, serial, 0);
|
||||||
|
if (!apk_path) {
|
||||||
|
LOGE("Could not get device apk path");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define PREFIX_SIZE (sizeof("CLASSPATH=") - 1)
|
||||||
|
size_t len = strlen(apk_path);
|
||||||
|
char *cp = malloc(PREFIX_SIZE + len + 1);
|
||||||
|
if (!cp) {
|
||||||
|
LOG_OOM();
|
||||||
|
free(apk_path);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(cp, "CLASSPATH=", PREFIX_SIZE);
|
||||||
|
memcpy(cp + PREFIX_SIZE, apk_path, len + 1);
|
||||||
|
free(apk_path);
|
||||||
|
return cp;
|
||||||
|
}
|
||||||
|
|
||||||
static sc_pid
|
static sc_pid
|
||||||
execute_server(struct sc_server *server,
|
execute_server(struct sc_server *server,
|
||||||
const struct sc_server_params *params) {
|
const struct sc_server_params *params) {
|
||||||
@ -160,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 = get_classpath_cmd(&server->intr, serial, params->install);
|
||||||
|
if (!classpath) {
|
||||||
|
return SC_PROCESS_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGD("Using %s", classpath);
|
||||||
|
|
||||||
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
|
||||||
@ -252,6 +316,10 @@ execute_server(struct sc_server *server,
|
|||||||
// By default, power_on is true
|
// By default, power_on is true
|
||||||
ADD_PARAM("power_on=false");
|
ADD_PARAM("power_on=false");
|
||||||
}
|
}
|
||||||
|
if (params->install) {
|
||||||
|
// By default, installed is false
|
||||||
|
ADD_PARAM("installed=true");
|
||||||
|
}
|
||||||
|
|
||||||
#undef ADD_PARAM
|
#undef ADD_PARAM
|
||||||
|
|
||||||
@ -272,6 +340,8 @@ execute_server(struct sc_server *server,
|
|||||||
pid = sc_adb_execute(cmd, 0);
|
pid = sc_adb_execute(cmd, 0);
|
||||||
|
|
||||||
end:
|
end:
|
||||||
|
free(classpath);
|
||||||
|
|
||||||
for (unsigned i = dyn_idx; i < count; ++i) {
|
for (unsigned i = dyn_idx; i < count; ++i) {
|
||||||
free((char *) cmd[i]);
|
free((char *) cmd[i]);
|
||||||
}
|
}
|
||||||
@ -755,8 +825,9 @@ 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, serial, params->install, params->reinstall);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
|
LOGE("Failed to push server");
|
||||||
goto error_connection_failed;
|
goto error_connection_failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,6 +48,8 @@ struct sc_server_params {
|
|||||||
bool select_tcpip;
|
bool select_tcpip;
|
||||||
bool cleanup;
|
bool cleanup;
|
||||||
bool power_on;
|
bool power_on;
|
||||||
|
bool install;
|
||||||
|
bool reinstall;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_server {
|
struct sc_server {
|
||||||
|
@ -92,8 +92,14 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags,
|
|||||||
close(in[0]);
|
close(in[0]);
|
||||||
}
|
}
|
||||||
close(in[1]);
|
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 (pout) {
|
||||||
if (out[1] != STDOUT_FILENO) {
|
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]);
|
close(out[0]);
|
||||||
} else if (!inherit_stdout) {
|
} else if (!inherit_stdout) {
|
||||||
// Close stdout in the child process
|
int devnull = open("/dev/null", O_WRONLY | O_CREAT, 0666);
|
||||||
close(STDOUT_FILENO);
|
if (devnull != -1) {
|
||||||
|
dup2(devnull, STDOUT_FILENO);
|
||||||
|
} else {
|
||||||
|
LOGE("Could not open /dev/null for stdout");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (perr) {
|
if (perr) {
|
||||||
@ -113,8 +123,12 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned flags,
|
|||||||
}
|
}
|
||||||
close(err[0]);
|
close(err[0]);
|
||||||
} else if (!inherit_stderr) {
|
} else if (!inherit_stderr) {
|
||||||
// Close stderr in the child process
|
int devnull = open("/dev/null", O_WRONLY | O_CREAT, 0666);
|
||||||
close(STDERR_FILENO);
|
if (devnull != -1) {
|
||||||
|
dup2(devnull, STDERR_FILENO);
|
||||||
|
} else {
|
||||||
|
LOGE("Could not open /dev/null for stderr");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
close(internal[0]);
|
close(internal[0]);
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
#ifndef SC_BUFFER_UTIL_H
|
#ifndef SC_BINARY_H
|
||||||
#define SC_BUFFER_UTIL_H
|
#define SC_BINARY_H
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
@ -43,4 +44,33 @@ sc_read64be(const uint8_t *buf) {
|
|||||||
return ((uint64_t) msb << 32) | lsb;
|
return ((uint64_t) msb << 32) | lsb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a float between 0 and 1 to an unsigned 16-bit fixed-point value
|
||||||
|
*/
|
||||||
|
static inline uint16_t
|
||||||
|
sc_float_to_u16fp(float f) {
|
||||||
|
assert(f >= 0.0f && f <= 1.0f);
|
||||||
|
uint32_t u = f * 0x1p16f; // 2^16
|
||||||
|
if (u >= 0xffff) {
|
||||||
|
assert(u == 0x10000); // for f == 1.0f
|
||||||
|
u = 0xffff;
|
||||||
|
}
|
||||||
|
return (uint16_t) u;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a float between -1 and 1 to a signed 16-bit fixed-point value
|
||||||
|
*/
|
||||||
|
static inline int16_t
|
||||||
|
sc_float_to_i16fp(float f) {
|
||||||
|
assert(f >= -1.0f && f <= 1.0f);
|
||||||
|
int32_t i = f * 0x1p15f; // 2^15
|
||||||
|
assert(i >= -0x8000);
|
||||||
|
if (i >= 0x7fff) {
|
||||||
|
assert(i == 0x8000); // for f == 1.0f
|
||||||
|
i = 0x7fff;
|
||||||
|
}
|
||||||
|
return (int16_t) i;
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
@ -241,6 +241,54 @@ static void test_get_ip_truncated(void) {
|
|||||||
assert(!ip);
|
assert(!ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_apk_path(void) {
|
||||||
|
char str[] = "package:/data/app/~~71mguyc6p-kNjQdNaNkToA==/com.genymobile."
|
||||||
|
"scrcpy-l6fiqqUSU7Ok7QLg-rIyJA==/base.apk=com.genymobile."
|
||||||
|
"scrcpy\n";
|
||||||
|
|
||||||
|
const char *expected = "/data/app/~~71mguyc6p-kNjQdNaNkToA==/com.genymobile"
|
||||||
|
".scrcpy-l6fiqqUSU7Ok7QLg-rIyJA==/base.apk";
|
||||||
|
char *path = sc_adb_parse_installed_apk_path(str);
|
||||||
|
assert(!strcmp(path, expected));
|
||||||
|
free(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_apk_path_invalid(void) {
|
||||||
|
// Does not start with "package:"
|
||||||
|
char str[] = "garbage:/data/app/~~71mguyc6p-kNjQdNaNkToA==/com.genymobile."
|
||||||
|
"scrcpy-l6fiqqUSU7Ok7QLg-rIyJA==/base.apk=com.genymobile."
|
||||||
|
"scrcpy\n";
|
||||||
|
|
||||||
|
char *path = sc_adb_parse_installed_apk_path(str);
|
||||||
|
assert(!path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_apk_version(void) {
|
||||||
|
char str[] =
|
||||||
|
"Key Set Manager:\n"
|
||||||
|
" [com.genymobile.scrcpy]\n"
|
||||||
|
" Signing KeySets: 128\n"
|
||||||
|
"\n"
|
||||||
|
"Packages:\n"
|
||||||
|
" Package [com.genymobile.scrcpy] (89abcdef):\n"
|
||||||
|
" userId=12345\n"
|
||||||
|
" pkg=Package{012345 com.genymobile.scrcpy}\n"
|
||||||
|
" codePath=/data/app/~~abcdef==/com.genymobile.scrcpy-012345==\n"
|
||||||
|
" resourcePath=/data/app/~~abcdef==/com.genymobile.scrcpy-013245==\n"
|
||||||
|
" primaryCpuAbi=null\n"
|
||||||
|
" secondaryCpuAbi=null\n"
|
||||||
|
" versionCode=12400 minSdk=21 targetSdk=31\n"
|
||||||
|
" versionName=1.24\n"
|
||||||
|
" splits=[base]\n"
|
||||||
|
" apkSigningVersion=2\n"
|
||||||
|
" applicationInfo=ApplicationInfo{012345 com.genymobile.scrcpy}\n";
|
||||||
|
|
||||||
|
const char *expected = "1.24";
|
||||||
|
char *version = sc_adb_parse_installed_apk_version(str);
|
||||||
|
assert(!strcmp(version, expected));
|
||||||
|
free(version);
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
(void) argc;
|
(void) argc;
|
||||||
(void) argv;
|
(void) argv;
|
||||||
@ -263,5 +311,9 @@ int main(int argc, char *argv[]) {
|
|||||||
test_get_ip_no_wlan_without_eol();
|
test_get_ip_no_wlan_without_eol();
|
||||||
test_get_ip_truncated();
|
test_get_ip_truncated();
|
||||||
|
|
||||||
|
test_apk_path();
|
||||||
|
test_apk_path_invalid();
|
||||||
|
test_apk_version();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
114
app/tests/test_binary.c
Normal file
114
app/tests/test_binary.c
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "util/binary.h"
|
||||||
|
|
||||||
|
static void test_write16be(void) {
|
||||||
|
uint16_t val = 0xABCD;
|
||||||
|
uint8_t buf[2];
|
||||||
|
|
||||||
|
sc_write16be(buf, val);
|
||||||
|
|
||||||
|
assert(buf[0] == 0xAB);
|
||||||
|
assert(buf[1] == 0xCD);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_write32be(void) {
|
||||||
|
uint32_t val = 0xABCD1234;
|
||||||
|
uint8_t buf[4];
|
||||||
|
|
||||||
|
sc_write32be(buf, val);
|
||||||
|
|
||||||
|
assert(buf[0] == 0xAB);
|
||||||
|
assert(buf[1] == 0xCD);
|
||||||
|
assert(buf[2] == 0x12);
|
||||||
|
assert(buf[3] == 0x34);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_write64be(void) {
|
||||||
|
uint64_t val = 0xABCD1234567890EF;
|
||||||
|
uint8_t buf[8];
|
||||||
|
|
||||||
|
sc_write64be(buf, val);
|
||||||
|
|
||||||
|
assert(buf[0] == 0xAB);
|
||||||
|
assert(buf[1] == 0xCD);
|
||||||
|
assert(buf[2] == 0x12);
|
||||||
|
assert(buf[3] == 0x34);
|
||||||
|
assert(buf[4] == 0x56);
|
||||||
|
assert(buf[5] == 0x78);
|
||||||
|
assert(buf[6] == 0x90);
|
||||||
|
assert(buf[7] == 0xEF);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_read16be(void) {
|
||||||
|
uint8_t buf[2] = {0xAB, 0xCD};
|
||||||
|
|
||||||
|
uint16_t val = sc_read16be(buf);
|
||||||
|
|
||||||
|
assert(val == 0xABCD);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_read32be(void) {
|
||||||
|
uint8_t buf[4] = {0xAB, 0xCD, 0x12, 0x34};
|
||||||
|
|
||||||
|
uint32_t val = sc_read32be(buf);
|
||||||
|
|
||||||
|
assert(val == 0xABCD1234);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_read64be(void) {
|
||||||
|
uint8_t buf[8] = {0xAB, 0xCD, 0x12, 0x34,
|
||||||
|
0x56, 0x78, 0x90, 0xEF};
|
||||||
|
|
||||||
|
uint64_t val = sc_read64be(buf);
|
||||||
|
|
||||||
|
assert(val == 0xABCD1234567890EF);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_float_to_u16fp(void) {
|
||||||
|
assert(sc_float_to_u16fp(0.0f) == 0);
|
||||||
|
assert(sc_float_to_u16fp(0.03125f) == 0x800);
|
||||||
|
assert(sc_float_to_u16fp(0.0625f) == 0x1000);
|
||||||
|
assert(sc_float_to_u16fp(0.125f) == 0x2000);
|
||||||
|
assert(sc_float_to_u16fp(0.25f) == 0x4000);
|
||||||
|
assert(sc_float_to_u16fp(0.5f) == 0x8000);
|
||||||
|
assert(sc_float_to_u16fp(0.75f) == 0xc000);
|
||||||
|
assert(sc_float_to_u16fp(1.0f) == 0xffff);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_float_to_i16fp(void) {
|
||||||
|
assert(sc_float_to_i16fp(0.0f) == 0);
|
||||||
|
assert(sc_float_to_i16fp(0.03125f) == 0x400);
|
||||||
|
assert(sc_float_to_i16fp(0.0625f) == 0x800);
|
||||||
|
assert(sc_float_to_i16fp(0.125f) == 0x1000);
|
||||||
|
assert(sc_float_to_i16fp(0.25f) == 0x2000);
|
||||||
|
assert(sc_float_to_i16fp(0.5f) == 0x4000);
|
||||||
|
assert(sc_float_to_i16fp(0.75f) == 0x6000);
|
||||||
|
assert(sc_float_to_i16fp(1.0f) == 0x7fff);
|
||||||
|
|
||||||
|
assert(sc_float_to_i16fp(-0.03125f) == -0x400);
|
||||||
|
assert(sc_float_to_i16fp(-0.0625f) == -0x800);
|
||||||
|
assert(sc_float_to_i16fp(-0.125f) == -0x1000);
|
||||||
|
assert(sc_float_to_i16fp(-0.25f) == -0x2000);
|
||||||
|
assert(sc_float_to_i16fp(-0.5f) == -0x4000);
|
||||||
|
assert(sc_float_to_i16fp(-0.75f) == -0x6000);
|
||||||
|
assert(sc_float_to_i16fp(-1.0f) == -0x8000);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
(void) argc;
|
||||||
|
(void) argv;
|
||||||
|
|
||||||
|
test_write16be();
|
||||||
|
test_write32be();
|
||||||
|
test_write64be();
|
||||||
|
test_read16be();
|
||||||
|
test_read32be();
|
||||||
|
test_read64be();
|
||||||
|
|
||||||
|
test_float_to_u16fp();
|
||||||
|
test_float_to_i16fp();
|
||||||
|
return 0;
|
||||||
|
}
|
@ -1,81 +0,0 @@
|
|||||||
#include "common.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
#include "util/buffer_util.h"
|
|
||||||
|
|
||||||
static void test_buffer_write16be(void) {
|
|
||||||
uint16_t val = 0xABCD;
|
|
||||||
uint8_t buf[2];
|
|
||||||
|
|
||||||
sc_write16be(buf, val);
|
|
||||||
|
|
||||||
assert(buf[0] == 0xAB);
|
|
||||||
assert(buf[1] == 0xCD);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_buffer_write32be(void) {
|
|
||||||
uint32_t val = 0xABCD1234;
|
|
||||||
uint8_t buf[4];
|
|
||||||
|
|
||||||
sc_write32be(buf, val);
|
|
||||||
|
|
||||||
assert(buf[0] == 0xAB);
|
|
||||||
assert(buf[1] == 0xCD);
|
|
||||||
assert(buf[2] == 0x12);
|
|
||||||
assert(buf[3] == 0x34);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_buffer_write64be(void) {
|
|
||||||
uint64_t val = 0xABCD1234567890EF;
|
|
||||||
uint8_t buf[8];
|
|
||||||
|
|
||||||
sc_write64be(buf, val);
|
|
||||||
|
|
||||||
assert(buf[0] == 0xAB);
|
|
||||||
assert(buf[1] == 0xCD);
|
|
||||||
assert(buf[2] == 0x12);
|
|
||||||
assert(buf[3] == 0x34);
|
|
||||||
assert(buf[4] == 0x56);
|
|
||||||
assert(buf[5] == 0x78);
|
|
||||||
assert(buf[6] == 0x90);
|
|
||||||
assert(buf[7] == 0xEF);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_buffer_read16be(void) {
|
|
||||||
uint8_t buf[2] = {0xAB, 0xCD};
|
|
||||||
|
|
||||||
uint16_t val = sc_read16be(buf);
|
|
||||||
|
|
||||||
assert(val == 0xABCD);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_buffer_read32be(void) {
|
|
||||||
uint8_t buf[4] = {0xAB, 0xCD, 0x12, 0x34};
|
|
||||||
|
|
||||||
uint32_t val = sc_read32be(buf);
|
|
||||||
|
|
||||||
assert(val == 0xABCD1234);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_buffer_read64be(void) {
|
|
||||||
uint8_t buf[8] = {0xAB, 0xCD, 0x12, 0x34,
|
|
||||||
0x56, 0x78, 0x90, 0xEF};
|
|
||||||
|
|
||||||
uint64_t val = sc_read64be(buf);
|
|
||||||
|
|
||||||
assert(val == 0xABCD1234567890EF);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
|
||||||
(void) argc;
|
|
||||||
(void) argv;
|
|
||||||
|
|
||||||
test_buffer_write16be();
|
|
||||||
test_buffer_write32be();
|
|
||||||
test_buffer_write64be();
|
|
||||||
test_buffer_read16be();
|
|
||||||
test_buffer_read32be();
|
|
||||||
test_buffer_read64be();
|
|
||||||
return 0;
|
|
||||||
}
|
|
@ -132,14 +132,14 @@ static void test_serialize_inject_scroll_event(void) {
|
|||||||
|
|
||||||
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
|
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
|
||||||
size_t size = sc_control_msg_serialize(&msg, buf);
|
size_t size = sc_control_msg_serialize(&msg, buf);
|
||||||
assert(size == 25);
|
assert(size == 21);
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
const unsigned char expected[] = {
|
||||||
SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
|
SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
|
||||||
0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x04, 0x02, // 260 1026
|
0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x04, 0x02, // 260 1026
|
||||||
0x04, 0x38, 0x07, 0x80, // 1080 1920
|
0x04, 0x38, 0x07, 0x80, // 1080 1920
|
||||||
0x00, 0x00, 0x00, 0x01, // 1
|
0x7F, 0xFF, // 1 (float encoded as i16)
|
||||||
0xFF, 0xFF, 0xFF, 0xFF, // -1
|
0x80, 0x00, // -1 (float encoded as i16)
|
||||||
0x00, 0x00, 0x00, 0x01, // 1
|
0x00, 0x00, 0x00, 0x01, // 1
|
||||||
};
|
};
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
|
@ -4,10 +4,10 @@ buildscript {
|
|||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
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
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
@ -17,7 +17,7 @@ buildscript {
|
|||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
tasks.withType(JavaCompile) {
|
tasks.withType(JavaCompile) {
|
||||||
options.compilerArgs << "-Xlint:deprecation"
|
options.compilerArgs << "-Xlint:deprecation"
|
||||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
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
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
2
run
2
run
@ -21,5 +21,5 @@ then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
SCRCPY_ICON_PATH="app/data/icon.png" \
|
SCRCPY_ICON_PATH="app/data/icon.png" \
|
||||||
SCRCPY_SERVER_PATH="$BUILDDIR/server/scrcpy-server" \
|
SCRCPY_SERVER_PATH="$BUILDDIR/server/scrcpy-server.apk" \
|
||||||
"$BUILDDIR/app/scrcpy" "$@"
|
"$BUILDDIR/app/scrcpy" "$@"
|
||||||
|
1
server/.gitignore
vendored
1
server/.gitignore
vendored
@ -6,3 +6,4 @@
|
|||||||
/build
|
/build
|
||||||
/captures
|
/captures
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
|
/keystore.properties
|
||||||
|
12
server/HOWTO_keystore.txt
Normal file
12
server/HOWTO_keystore.txt
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
For an APK to be installable, it must be signed: <https://developer.android.com/training/articles/keystore>
|
||||||
|
|
||||||
|
For that purpose, create a keystore by executing this command:
|
||||||
|
|
||||||
|
keytool -genkey -v -keystore ~/.android/scrcpy.keystore -keyalg RSA -keysize 2048 -validity 10000 -alias scrcpy -dname cn=scrcpy
|
||||||
|
|
||||||
|
(Adapt ~/.android/scrcpy.keystore if you want to generate it to another location.)
|
||||||
|
|
||||||
|
Then create server/keystore.properties and edit its properties:
|
||||||
|
|
||||||
|
cp keystore.properties.sample keystore.properties
|
||||||
|
vim keystore.properties # fill the properties
|
@ -1,19 +1,35 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 31
|
compileSdkVersion 33
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.genymobile.scrcpy"
|
applicationId "com.genymobile.scrcpy"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 31
|
targetSdkVersion 33
|
||||||
versionCode 12400
|
versionCode 12400
|
||||||
versionName "1.24"
|
versionName "1.24"
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
signingConfigs {
|
||||||
|
release {
|
||||||
|
// to be defined in server/keystore.properties (see server/HOWTO_keystore.txt)
|
||||||
|
def keystorePropsFile = rootProject.file("server/keystore.properties")
|
||||||
|
if (keystorePropsFile.exists()) {
|
||||||
|
def props = new Properties()
|
||||||
|
props.load(new FileInputStream(keystorePropsFile))
|
||||||
|
|
||||||
|
storeFile rootProject.file(props['storeFile'])
|
||||||
|
storePassword props['storePassword']
|
||||||
|
keyAlias props['keyAlias']
|
||||||
|
keyPassword props['keyPassword']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
minifyEnabled false
|
minifyEnabled false
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
signingConfig signingConfigs.release
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,13 +14,42 @@ set -e
|
|||||||
SCRCPY_DEBUG=false
|
SCRCPY_DEBUG=false
|
||||||
SCRCPY_VERSION_NAME=1.24
|
SCRCPY_VERSION_NAME=1.24
|
||||||
|
|
||||||
PLATFORM=${ANDROID_PLATFORM:-31}
|
SERVER_DIR="$(realpath $(dirname "$0"))"
|
||||||
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-31.0.0}
|
KEYSTORE_PROPERTIES_FILE="$SERVER_DIR/keystore.properties"
|
||||||
|
|
||||||
|
if [[ ! -f "$KEYSTORE_PROPERTIES_FILE" ]]
|
||||||
|
then
|
||||||
|
echo "The file '$KEYSTORE_PROPERTIES_FILE' does not exist." >&2
|
||||||
|
echo "Please read '$SERVER_DIR/HOWTO_keystore.txt'." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
declare -A props
|
||||||
|
while IFS='=' read -r key value
|
||||||
|
do
|
||||||
|
props["$key"]="$value"
|
||||||
|
done < "$KEYSTORE_PROPERTIES_FILE"
|
||||||
|
|
||||||
|
KEYSTORE_FILE=${props['storeFile']}
|
||||||
|
KEYSTORE_PASSWORD=${props['storePassword']}
|
||||||
|
KEYSTORE_KEY_ALIAS=${props['keyAlias']}
|
||||||
|
KEYSTORE_KEY_PASSWORD=${props['keyPassword']}
|
||||||
|
|
||||||
|
if [[ ! -f "$KEYSTORE_FILE" ]]
|
||||||
|
then
|
||||||
|
echo "Keystore '$KEYSTORE_FILE' (read from '$KEYSTORE_PROPERTIES_FILE')" \
|
||||||
|
"does not exist." >&2
|
||||||
|
echo "Please read '$SERVER_DIR/HOWTO_keystore.txt'." >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
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})"
|
BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})"
|
||||||
CLASSES_DIR="$BUILD_DIR/classes"
|
CLASSES_DIR="$BUILD_DIR/classes"
|
||||||
SERVER_DIR=$(dirname "$0")
|
SERVER_BINARY=scrcpy-server.apk
|
||||||
SERVER_BINARY=scrcpy-server
|
|
||||||
ANDROID_JAR="$ANDROID_HOME/platforms/android-$PLATFORM/android.jar"
|
ANDROID_JAR="$ANDROID_HOME/platforms/android-$PLATFORM/android.jar"
|
||||||
|
|
||||||
echo "Platform: android-$PLATFORM"
|
echo "Platform: android-$PLATFORM"
|
||||||
@ -41,9 +70,8 @@ EOF
|
|||||||
|
|
||||||
echo "Generating java from aidl..."
|
echo "Generating java from aidl..."
|
||||||
cd "$SERVER_DIR/src/main/aidl"
|
cd "$SERVER_DIR/src/main/aidl"
|
||||||
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/aidl" -o"$CLASSES_DIR" \
|
"$BUILD_TOOLS_DIR/aidl" -o"$CLASSES_DIR" android/view/IRotationWatcher.aidl
|
||||||
android/view/IRotationWatcher.aidl
|
"$BUILD_TOOLS_DIR/aidl" -o"$CLASSES_DIR" \
|
||||||
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/aidl" -o"$CLASSES_DIR" \
|
|
||||||
android/content/IOnPrimaryClipChangedListener.aidl
|
android/content/IOnPrimaryClipChangedListener.aidl
|
||||||
|
|
||||||
echo "Compiling java sources..."
|
echo "Compiling java sources..."
|
||||||
@ -59,20 +87,15 @@ cd "$CLASSES_DIR"
|
|||||||
if [[ $PLATFORM -lt 31 ]]
|
if [[ $PLATFORM -lt 31 ]]
|
||||||
then
|
then
|
||||||
# use dx
|
# use dx
|
||||||
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/dx" --dex \
|
"$BUILD_TOOLS_DIR/dx" --dex --output "$BUILD_DIR/classes.dex" \
|
||||||
--output "$BUILD_DIR/classes.dex" \
|
|
||||||
android/view/*.class \
|
android/view/*.class \
|
||||||
android/content/*.class \
|
android/content/*.class \
|
||||||
com/genymobile/scrcpy/*.class \
|
com/genymobile/scrcpy/*.class \
|
||||||
com/genymobile/scrcpy/wrappers/*.class
|
com/genymobile/scrcpy/wrappers/*.class
|
||||||
|
|
||||||
echo "Archiving..."
|
|
||||||
cd "$BUILD_DIR"
|
cd "$BUILD_DIR"
|
||||||
jar cvf "$SERVER_BINARY" classes.dex
|
|
||||||
rm -rf classes.dex classes
|
|
||||||
else
|
else
|
||||||
# use d8
|
# use d8
|
||||||
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/d8" --classpath "$ANDROID_JAR" \
|
"$BUILD_TOOLS_DIR/d8" --classpath "$ANDROID_JAR" \
|
||||||
--output "$BUILD_DIR/classes.zip" \
|
--output "$BUILD_DIR/classes.zip" \
|
||||||
android/view/*.class \
|
android/view/*.class \
|
||||||
android/content/*.class \
|
android/content/*.class \
|
||||||
@ -80,8 +103,24 @@ else
|
|||||||
com/genymobile/scrcpy/wrappers/*.class
|
com/genymobile/scrcpy/wrappers/*.class
|
||||||
|
|
||||||
cd "$BUILD_DIR"
|
cd "$BUILD_DIR"
|
||||||
mv classes.zip "$SERVER_BINARY"
|
unzip -o classes.zip classes.dex # we need the inner classes.dex
|
||||||
rm -rf classes
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo "Packaging..."
|
||||||
|
# note: if a res directory exists, add: -S "$SERVER_DIR/src/main/res"
|
||||||
|
"$BUILD_TOOLS_DIR/aapt" package -f \
|
||||||
|
-M "$SERVER_DIR/src/main/AndroidManifest.xml" \
|
||||||
|
-I "$ANDROID_JAR" \
|
||||||
|
-F "$SERVER_BINARY.unaligned"
|
||||||
|
"$BUILD_TOOLS_DIR/aapt" add "$SERVER_BINARY.unaligned" classes.dex
|
||||||
|
"$BUILD_TOOLS_DIR/zipalign" -p 4 "$SERVER_BINARY.unaligned" "$SERVER_BINARY"
|
||||||
|
rm "$SERVER_BINARY.unaligned"
|
||||||
|
|
||||||
|
"$BUILD_TOOLS_DIR/apksigner" sign \
|
||||||
|
--ks "$KEYSTORE_FILE" \
|
||||||
|
--ks-pass "pass:$KEYSTORE_PASSWORD" \
|
||||||
|
--ks-key-alias "$KEYSTORE_KEY_ALIAS" \
|
||||||
|
--key-pass "pass:$KEYSTORE_KEY_PASSWORD" \
|
||||||
|
"$SERVER_BINARY"
|
||||||
|
|
||||||
echo "Server generated in $BUILD_DIR/$SERVER_BINARY"
|
echo "Server generated in $BUILD_DIR/$SERVER_BINARY"
|
||||||
|
4
server/keystore.properties.sample
Normal file
4
server/keystore.properties.sample
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
storeFile=/path/to/your/keystore
|
||||||
|
storePassword=PASSWORD
|
||||||
|
keyAlias=scrcpy
|
||||||
|
keyPassword=PASSWORD
|
@ -6,7 +6,7 @@ if prebuilt_server == ''
|
|||||||
# gradle is responsible for tracking source changes
|
# gradle is responsible for tracking source changes
|
||||||
build_by_default: true,
|
build_by_default: true,
|
||||||
build_always_stale: true,
|
build_always_stale: true,
|
||||||
output: 'scrcpy-server',
|
output: 'scrcpy-server.apk',
|
||||||
command: [find_program('./scripts/build-wrapper.sh'), meson.current_source_dir(), '@OUTPUT@', get_option('buildtype')],
|
command: [find_program('./scripts/build-wrapper.sh'), meson.current_source_dir(), '@OUTPUT@', get_option('buildtype')],
|
||||||
console: true,
|
console: true,
|
||||||
install: true,
|
install: true,
|
||||||
@ -18,7 +18,7 @@ else
|
|||||||
endif
|
endif
|
||||||
custom_target('scrcpy-server-prebuilt',
|
custom_target('scrcpy-server-prebuilt',
|
||||||
input: prebuilt_server,
|
input: prebuilt_server,
|
||||||
output: 'scrcpy-server',
|
output: 'scrcpy-server.apk',
|
||||||
command: ['cp', '@INPUT@', '@OUTPUT@'],
|
command: ['cp', '@INPUT@', '@OUTPUT@'],
|
||||||
install: true,
|
install: true,
|
||||||
install_dir: 'share/scrcpy')
|
install_dir: 'share/scrcpy')
|
||||||
|
@ -25,5 +25,5 @@ then
|
|||||||
cp "$PROJECT_ROOT/build/outputs/apk/debug/server-debug.apk" "$OUTPUT"
|
cp "$PROJECT_ROOT/build/outputs/apk/debug/server-debug.apk" "$OUTPUT"
|
||||||
else
|
else
|
||||||
"$GRADLE" -p "$PROJECT_ROOT" assembleRelease
|
"$GRADLE" -p "$PROJECT_ROOT" assembleRelease
|
||||||
cp "$PROJECT_ROOT/build/outputs/apk/release/server-release-unsigned.apk" "$OUTPUT"
|
cp "$PROJECT_ROOT/build/outputs/apk/release/server-release.apk" "$OUTPUT"
|
||||||
fi
|
fi
|
||||||
|
38
server/src/main/java/com/genymobile/scrcpy/Binary.java
Normal file
38
server/src/main/java/com/genymobile/scrcpy/Binary.java
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
public final class Binary {
|
||||||
|
private Binary() {
|
||||||
|
// not instantiable
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int toUnsigned(short value) {
|
||||||
|
return value & 0xffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int toUnsigned(byte value) {
|
||||||
|
return value & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert unsigned 16-bit fixed-point to a float between 0 and 1
|
||||||
|
*
|
||||||
|
* @param value encoded value
|
||||||
|
* @return Float value between 0 and 1
|
||||||
|
*/
|
||||||
|
public static float u16FixedPointToFloat(short value) {
|
||||||
|
int unsignedShort = Binary.toUnsigned(value);
|
||||||
|
// 0x1p16f is 2^16 as float
|
||||||
|
return unsignedShort == 0xffff ? 1f : (unsignedShort / 0x1p16f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert signed 16-bit fixed-point to a float between -1 and 1
|
||||||
|
*
|
||||||
|
* @param value encoded value
|
||||||
|
* @return Float value between -1 and 1
|
||||||
|
*/
|
||||||
|
public static float i16FixedPointToFloat(short value) {
|
||||||
|
// 0x1p15f is 2^15 as float
|
||||||
|
return value == 0x7fff ? 1f : (value / 0x1p15f);
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,5 @@
|
|||||||
package com.genymobile.scrcpy;
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
import com.genymobile.scrcpy.wrappers.ServiceManager;
|
|
||||||
|
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
@ -16,7 +14,7 @@ import java.io.IOException;
|
|||||||
*/
|
*/
|
||||||
public final class CleanUp {
|
public final class CleanUp {
|
||||||
|
|
||||||
public static final String SERVER_PATH = "/data/local/tmp/scrcpy-server.jar";
|
public static final String SERVER_PATH = "/data/local/tmp/scrcpy-server.apk";
|
||||||
|
|
||||||
// A simple struct to be passed from the main process to the cleanup process
|
// A simple struct to be passed from the main process to the cleanup process
|
||||||
public static class Config implements Parcelable {
|
public static class Config implements Parcelable {
|
||||||
@ -37,6 +35,8 @@ public final class CleanUp {
|
|||||||
private static final int FLAG_RESTORE_NORMAL_POWER_MODE = 2;
|
private static final int FLAG_RESTORE_NORMAL_POWER_MODE = 2;
|
||||||
private static final int FLAG_POWER_OFF_SCREEN = 4;
|
private static final int FLAG_POWER_OFF_SCREEN = 4;
|
||||||
|
|
||||||
|
private boolean installed;
|
||||||
|
|
||||||
private int displayId;
|
private int displayId;
|
||||||
|
|
||||||
// Restore the value (between 0 and 7), -1 to not restore
|
// Restore the value (between 0 and 7), -1 to not restore
|
||||||
@ -52,6 +52,7 @@ public final class CleanUp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected Config(Parcel in) {
|
protected Config(Parcel in) {
|
||||||
|
installed = in.readInt() != 0;
|
||||||
displayId = in.readInt();
|
displayId = in.readInt();
|
||||||
restoreStayOn = in.readInt();
|
restoreStayOn = in.readInt();
|
||||||
byte options = in.readByte();
|
byte options = in.readByte();
|
||||||
@ -62,6 +63,7 @@ public final class CleanUp {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeInt(installed ? 1 : 0);
|
||||||
dest.writeInt(displayId);
|
dest.writeInt(displayId);
|
||||||
dest.writeInt(restoreStayOn);
|
dest.writeInt(restoreStayOn);
|
||||||
byte options = 0;
|
byte options = 0;
|
||||||
@ -116,9 +118,10 @@ public final class CleanUp {
|
|||||||
// not instantiable
|
// not instantiable
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void configure(int displayId, int restoreStayOn, boolean disableShowTouches, boolean restoreNormalPowerMode, boolean powerOffScreen)
|
public static void configure(boolean installed, int displayId, int restoreStayOn, boolean disableShowTouches, boolean restoreNormalPowerMode,
|
||||||
throws IOException {
|
boolean powerOffScreen) throws IOException {
|
||||||
Config config = new Config();
|
Config config = new Config();
|
||||||
|
config.installed = installed;
|
||||||
config.displayId = displayId;
|
config.displayId = displayId;
|
||||||
config.disableShowTouches = disableShowTouches;
|
config.disableShowTouches = disableShowTouches;
|
||||||
config.restoreStayOn = restoreStayOn;
|
config.restoreStayOn = restoreStayOn;
|
||||||
@ -127,8 +130,9 @@ public final class CleanUp {
|
|||||||
|
|
||||||
if (config.hasWork()) {
|
if (config.hasWork()) {
|
||||||
startProcess(config);
|
startProcess(config);
|
||||||
} else {
|
} else if (!installed) {
|
||||||
// There is no additional clean up to do when scrcpy dies
|
// There is no additional clean up to do when scrcpy dies.
|
||||||
|
// If the APK has been pushed to /data/local/tmp, remove it.
|
||||||
unlinkSelf();
|
unlinkSelf();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -137,7 +141,8 @@ public final class CleanUp {
|
|||||||
String[] cmd = {"app_process", "/", CleanUp.class.getName(), config.toBase64()};
|
String[] cmd = {"app_process", "/", CleanUp.class.getName(), config.toBase64()};
|
||||||
|
|
||||||
ProcessBuilder builder = new ProcessBuilder(cmd);
|
ProcessBuilder builder = new ProcessBuilder(cmd);
|
||||||
builder.environment().put("CLASSPATH", SERVER_PATH);
|
String serverPath = config.installed ? Device.getInstalledApkPath() : SERVER_PATH;
|
||||||
|
builder.environment().put("CLASSPATH", serverPath);
|
||||||
builder.start();
|
builder.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,7 +155,12 @@ public final class CleanUp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String... args) {
|
public static void main(String... args) {
|
||||||
unlinkSelf();
|
Config config = Config.fromBase64(args[0]);
|
||||||
|
|
||||||
|
if (!config.installed) {
|
||||||
|
// If the APK has been pushed to /data/local/tmp, remove it.
|
||||||
|
unlinkSelf();
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Wait for the server to die
|
// Wait for the server to die
|
||||||
@ -161,15 +171,11 @@ public final class CleanUp {
|
|||||||
|
|
||||||
Ln.i("Cleaning up");
|
Ln.i("Cleaning up");
|
||||||
|
|
||||||
Config config = Config.fromBase64(args[0]);
|
|
||||||
|
|
||||||
if (config.disableShowTouches || config.restoreStayOn != -1) {
|
if (config.disableShowTouches || config.restoreStayOn != -1) {
|
||||||
ServiceManager serviceManager = new ServiceManager();
|
|
||||||
Settings settings = new Settings(serviceManager);
|
|
||||||
if (config.disableShowTouches) {
|
if (config.disableShowTouches) {
|
||||||
Ln.i("Disabling \"show touches\"");
|
Ln.i("Disabling \"show touches\"");
|
||||||
try {
|
try {
|
||||||
settings.putValue(Settings.TABLE_SYSTEM, "show_touches", "0");
|
Settings.putValue(Settings.TABLE_SYSTEM, "show_touches", "0");
|
||||||
} catch (SettingsException e) {
|
} catch (SettingsException e) {
|
||||||
Ln.e("Could not restore \"show_touches\"", e);
|
Ln.e("Could not restore \"show_touches\"", e);
|
||||||
}
|
}
|
||||||
@ -177,7 +183,7 @@ public final class CleanUp {
|
|||||||
if (config.restoreStayOn != -1) {
|
if (config.restoreStayOn != -1) {
|
||||||
Ln.i("Restoring \"stay awake\"");
|
Ln.i("Restoring \"stay awake\"");
|
||||||
try {
|
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) {
|
} catch (SettingsException e) {
|
||||||
Ln.e("Could not restore \"stay_on_while_plugged_in\"", e);
|
Ln.e("Could not restore \"stay_on_while_plugged_in\"", e);
|
||||||
}
|
}
|
||||||
|
@ -30,4 +30,14 @@ public final class Command {
|
|||||||
}
|
}
|
||||||
return result;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,8 +33,8 @@ public final class ControlMessage {
|
|||||||
private long pointerId;
|
private long pointerId;
|
||||||
private float pressure;
|
private float pressure;
|
||||||
private Position position;
|
private Position position;
|
||||||
private int hScroll;
|
private float hScroll;
|
||||||
private int vScroll;
|
private float vScroll;
|
||||||
private int copyKey;
|
private int copyKey;
|
||||||
private boolean paste;
|
private boolean paste;
|
||||||
private int repeat;
|
private int repeat;
|
||||||
@ -71,7 +71,7 @@ public final class ControlMessage {
|
|||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ControlMessage createInjectScrollEvent(Position position, int hScroll, int vScroll, int buttons) {
|
public static ControlMessage createInjectScrollEvent(Position position, float hScroll, float vScroll, int buttons) {
|
||||||
ControlMessage msg = new ControlMessage();
|
ControlMessage msg = new ControlMessage();
|
||||||
msg.type = TYPE_INJECT_SCROLL_EVENT;
|
msg.type = TYPE_INJECT_SCROLL_EVENT;
|
||||||
msg.position = position;
|
msg.position = position;
|
||||||
@ -156,11 +156,11 @@ public final class ControlMessage {
|
|||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getHScroll() {
|
public float getHScroll() {
|
||||||
return hScroll;
|
return hScroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getVScroll() {
|
public float getVScroll() {
|
||||||
return vScroll;
|
return vScroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ public class ControlMessageReader {
|
|||||||
|
|
||||||
static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13;
|
static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13;
|
||||||
static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27;
|
static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27;
|
||||||
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 24;
|
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
||||||
static final int BACK_OR_SCREEN_ON_LENGTH = 1;
|
static final int BACK_OR_SCREEN_ON_LENGTH = 1;
|
||||||
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
|
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
|
||||||
static final int GET_CLIPBOARD_LENGTH = 1;
|
static final int GET_CLIPBOARD_LENGTH = 1;
|
||||||
@ -103,7 +103,7 @@ public class ControlMessageReader {
|
|||||||
if (buffer.remaining() < INJECT_KEYCODE_PAYLOAD_LENGTH) {
|
if (buffer.remaining() < INJECT_KEYCODE_PAYLOAD_LENGTH) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
int action = toUnsigned(buffer.get());
|
int action = Binary.toUnsigned(buffer.get());
|
||||||
int keycode = buffer.getInt();
|
int keycode = buffer.getInt();
|
||||||
int repeat = buffer.getInt();
|
int repeat = buffer.getInt();
|
||||||
int metaState = buffer.getInt();
|
int metaState = buffer.getInt();
|
||||||
@ -136,13 +136,10 @@ public class ControlMessageReader {
|
|||||||
if (buffer.remaining() < INJECT_TOUCH_EVENT_PAYLOAD_LENGTH) {
|
if (buffer.remaining() < INJECT_TOUCH_EVENT_PAYLOAD_LENGTH) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
int action = toUnsigned(buffer.get());
|
int action = Binary.toUnsigned(buffer.get());
|
||||||
long pointerId = buffer.getLong();
|
long pointerId = buffer.getLong();
|
||||||
Position position = readPosition(buffer);
|
Position position = readPosition(buffer);
|
||||||
// 16 bits fixed-point
|
float pressure = Binary.u16FixedPointToFloat(buffer.getShort());
|
||||||
int pressureInt = toUnsigned(buffer.getShort());
|
|
||||||
// convert it to a float between 0 and 1 (0x1p16f is 2^16 as float)
|
|
||||||
float pressure = pressureInt == 0xffff ? 1f : (pressureInt / 0x1p16f);
|
|
||||||
int buttons = buffer.getInt();
|
int buttons = buffer.getInt();
|
||||||
return ControlMessage.createInjectTouchEvent(action, pointerId, position, pressure, buttons);
|
return ControlMessage.createInjectTouchEvent(action, pointerId, position, pressure, buttons);
|
||||||
}
|
}
|
||||||
@ -152,8 +149,8 @@ public class ControlMessageReader {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Position position = readPosition(buffer);
|
Position position = readPosition(buffer);
|
||||||
int hScroll = buffer.getInt();
|
float hScroll = Binary.i16FixedPointToFloat(buffer.getShort());
|
||||||
int vScroll = buffer.getInt();
|
float vScroll = Binary.i16FixedPointToFloat(buffer.getShort());
|
||||||
int buttons = buffer.getInt();
|
int buttons = buffer.getInt();
|
||||||
return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll, buttons);
|
return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll, buttons);
|
||||||
}
|
}
|
||||||
@ -162,7 +159,7 @@ public class ControlMessageReader {
|
|||||||
if (buffer.remaining() < BACK_OR_SCREEN_ON_LENGTH) {
|
if (buffer.remaining() < BACK_OR_SCREEN_ON_LENGTH) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
int action = toUnsigned(buffer.get());
|
int action = Binary.toUnsigned(buffer.get());
|
||||||
return ControlMessage.createBackOrScreenOn(action);
|
return ControlMessage.createBackOrScreenOn(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,7 +167,7 @@ public class ControlMessageReader {
|
|||||||
if (buffer.remaining() < GET_CLIPBOARD_LENGTH) {
|
if (buffer.remaining() < GET_CLIPBOARD_LENGTH) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
int copyKey = toUnsigned(buffer.get());
|
int copyKey = Binary.toUnsigned(buffer.get());
|
||||||
return ControlMessage.createGetClipboard(copyKey);
|
return ControlMessage.createGetClipboard(copyKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,16 +195,8 @@ public class ControlMessageReader {
|
|||||||
private static Position readPosition(ByteBuffer buffer) {
|
private static Position readPosition(ByteBuffer buffer) {
|
||||||
int x = buffer.getInt();
|
int x = buffer.getInt();
|
||||||
int y = buffer.getInt();
|
int y = buffer.getInt();
|
||||||
int screenWidth = toUnsigned(buffer.getShort());
|
int screenWidth = Binary.toUnsigned(buffer.getShort());
|
||||||
int screenHeight = toUnsigned(buffer.getShort());
|
int screenHeight = Binary.toUnsigned(buffer.getShort());
|
||||||
return new Position(x, y, screenWidth, screenHeight);
|
return new Position(x, y, screenWidth, screenHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int toUnsigned(short value) {
|
|
||||||
return value & 0xffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int toUnsigned(byte value) {
|
|
||||||
return value & 0xff;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -223,7 +223,7 @@ public class Controller {
|
|||||||
return device.injectEvent(event, Device.INJECT_MODE_ASYNC);
|
return device.injectEvent(event, Device.INJECT_MODE_ASYNC);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean injectScroll(Position position, int hScroll, int vScroll, int buttons) {
|
private boolean injectScroll(Position position, float hScroll, float vScroll, int buttons) {
|
||||||
long now = SystemClock.uptimeMillis();
|
long now = SystemClock.uptimeMillis();
|
||||||
Point point = device.getPhysicalPoint(position);
|
Point point = device.getPhysicalPoint(position);
|
||||||
if (point == null) {
|
if (point == null) {
|
||||||
|
@ -7,6 +7,7 @@ import com.genymobile.scrcpy.wrappers.SurfaceControl;
|
|||||||
import com.genymobile.scrcpy.wrappers.WindowManager;
|
import com.genymobile.scrcpy.wrappers.WindowManager;
|
||||||
|
|
||||||
import android.content.IOnPrimaryClipChangedListener;
|
import android.content.IOnPrimaryClipChangedListener;
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
@ -21,6 +22,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
|
|
||||||
public final class Device {
|
public final class Device {
|
||||||
|
|
||||||
|
private static final String SCRCPY_PACKAGE_NAME = "com.genymobile.scrcpy";
|
||||||
|
|
||||||
public static final int POWER_MODE_OFF = SurfaceControl.POWER_MODE_OFF;
|
public static final int POWER_MODE_OFF = SurfaceControl.POWER_MODE_OFF;
|
||||||
public static final int POWER_MODE_NORMAL = SurfaceControl.POWER_MODE_NORMAL;
|
public static final int POWER_MODE_NORMAL = SurfaceControl.POWER_MODE_NORMAL;
|
||||||
|
|
||||||
@ -31,9 +34,6 @@ public final class Device {
|
|||||||
public static final int LOCK_VIDEO_ORIENTATION_UNLOCKED = -1;
|
public static final int LOCK_VIDEO_ORIENTATION_UNLOCKED = -1;
|
||||||
public static final int LOCK_VIDEO_ORIENTATION_INITIAL = -2;
|
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 {
|
public interface RotationListener {
|
||||||
void onRotationChanged(int rotation);
|
void onRotationChanged(int rotation);
|
||||||
}
|
}
|
||||||
@ -66,9 +66,9 @@ public final class Device {
|
|||||||
|
|
||||||
public Device(Options options) {
|
public Device(Options options) {
|
||||||
displayId = options.getDisplayId();
|
displayId = options.getDisplayId();
|
||||||
DisplayInfo displayInfo = SERVICE_MANAGER.getDisplayManager().getDisplayInfo(displayId);
|
DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId);
|
||||||
if (displayInfo == null) {
|
if (displayInfo == null) {
|
||||||
int[] displayIds = SERVICE_MANAGER.getDisplayManager().getDisplayIds();
|
int[] displayIds = ServiceManager.getDisplayManager().getDisplayIds();
|
||||||
throw new InvalidDisplayIdException(displayId, displayIds);
|
throw new InvalidDisplayIdException(displayId, displayIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ public final class Device {
|
|||||||
screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), deviceSize, crop, maxSize, lockVideoOrientation);
|
screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), deviceSize, crop, maxSize, lockVideoOrientation);
|
||||||
layerStack = displayInfo.getLayerStack();
|
layerStack = displayInfo.getLayerStack();
|
||||||
|
|
||||||
SERVICE_MANAGER.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() {
|
ServiceManager.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() {
|
||||||
@Override
|
@Override
|
||||||
public void onRotationChanged(int rotation) {
|
public void onRotationChanged(int rotation) {
|
||||||
synchronized (Device.this) {
|
synchronized (Device.this) {
|
||||||
@ -98,7 +98,7 @@ public final class Device {
|
|||||||
|
|
||||||
if (options.getControl() && options.getClipboardAutosync()) {
|
if (options.getControl() && options.getClipboardAutosync()) {
|
||||||
// If control and autosync are enabled, synchronize Android clipboard to the computer automatically
|
// 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) {
|
if (clipboardManager != null) {
|
||||||
clipboardManager.addPrimaryClipChangedListener(new IOnPrimaryClipChangedListener.Stub() {
|
clipboardManager.addPrimaryClipChangedListener(new IOnPrimaryClipChangedListener.Stub() {
|
||||||
@Override
|
@Override
|
||||||
@ -192,7 +192,7 @@ public final class Device {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return SERVICE_MANAGER.getInputManager().injectInputEvent(inputEvent, injectMode);
|
return ServiceManager.getInputManager().injectInputEvent(inputEvent, injectMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean injectEvent(InputEvent event, int injectMode) {
|
public boolean injectEvent(InputEvent event, int injectMode) {
|
||||||
@ -220,7 +220,7 @@ public final class Device {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isScreenOn() {
|
public static boolean isScreenOn() {
|
||||||
return SERVICE_MANAGER.getPowerManager().isScreenOn();
|
return ServiceManager.getPowerManager().isScreenOn();
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void setRotationListener(RotationListener rotationListener) {
|
public synchronized void setRotationListener(RotationListener rotationListener) {
|
||||||
@ -232,19 +232,19 @@ public final class Device {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void expandNotificationPanel() {
|
public static void expandNotificationPanel() {
|
||||||
SERVICE_MANAGER.getStatusBarManager().expandNotificationsPanel();
|
ServiceManager.getStatusBarManager().expandNotificationsPanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void expandSettingsPanel() {
|
public static void expandSettingsPanel() {
|
||||||
SERVICE_MANAGER.getStatusBarManager().expandSettingsPanel();
|
ServiceManager.getStatusBarManager().expandSettingsPanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void collapsePanels() {
|
public static void collapsePanels() {
|
||||||
SERVICE_MANAGER.getStatusBarManager().collapsePanels();
|
ServiceManager.getStatusBarManager().collapsePanels();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getClipboardText() {
|
public static String getClipboardText() {
|
||||||
ClipboardManager clipboardManager = SERVICE_MANAGER.getClipboardManager();
|
ClipboardManager clipboardManager = ServiceManager.getClipboardManager();
|
||||||
if (clipboardManager == null) {
|
if (clipboardManager == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -256,7 +256,7 @@ public final class Device {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean setClipboardText(String text) {
|
public boolean setClipboardText(String text) {
|
||||||
ClipboardManager clipboardManager = SERVICE_MANAGER.getClipboardManager();
|
ClipboardManager clipboardManager = ServiceManager.getClipboardManager();
|
||||||
if (clipboardManager == null) {
|
if (clipboardManager == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -299,7 +299,7 @@ public final class Device {
|
|||||||
* Disable auto-rotation (if enabled), set the screen rotation and re-enable auto-rotation (if it was enabled).
|
* Disable auto-rotation (if enabled), set the screen rotation and re-enable auto-rotation (if it was enabled).
|
||||||
*/
|
*/
|
||||||
public static void rotateDevice() {
|
public static void rotateDevice() {
|
||||||
WindowManager wm = SERVICE_MANAGER.getWindowManager();
|
WindowManager wm = ServiceManager.getWindowManager();
|
||||||
|
|
||||||
boolean accelerometerRotation = !wm.isRotationFrozen();
|
boolean accelerometerRotation = !wm.isRotationFrozen();
|
||||||
|
|
||||||
@ -316,7 +316,11 @@ public final class Device {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Settings getSettings() {
|
public static String getInstalledApkPath() {
|
||||||
return SETTINGS;
|
ApplicationInfo info = ServiceManager.getPackageManager().getApplicationInfo(SCRCPY_PACKAGE_NAME);
|
||||||
|
if (info == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return info.sourceDir;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,9 @@ import android.system.OsConstants;
|
|||||||
|
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
public final class IO {
|
public final class IO {
|
||||||
private 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 {
|
public static void writeFully(FileDescriptor fd, byte[] buffer, int offset, int len) throws IOException {
|
||||||
writeFully(fd, ByteBuffer.wrap(buffer, offset, len));
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ public class Options {
|
|||||||
private boolean downsizeOnError = true;
|
private boolean downsizeOnError = true;
|
||||||
private boolean cleanup = true;
|
private boolean cleanup = true;
|
||||||
private boolean powerOn = true;
|
private boolean powerOn = true;
|
||||||
|
private boolean installed = false;
|
||||||
|
|
||||||
// Options not used by the scrcpy client, but useful to use scrcpy-server directly
|
// Options not used by the scrcpy client, but useful to use scrcpy-server directly
|
||||||
private boolean sendDeviceMeta = true; // send device name and size
|
private boolean sendDeviceMeta = true; // send device name and size
|
||||||
@ -196,4 +197,12 @@ public class Options {
|
|||||||
public void setSendDummyByte(boolean sendDummyByte) {
|
public void setSendDummyByte(boolean sendDummyByte) {
|
||||||
this.sendDummyByte = sendDummyByte;
|
this.sendDummyByte = sendDummyByte;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean getInstalled() {
|
||||||
|
return installed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInstalled(boolean installed) {
|
||||||
|
this.installed = installed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,10 +20,9 @@ public final class Server {
|
|||||||
int restoreStayOn = -1;
|
int restoreStayOn = -1;
|
||||||
boolean restoreNormalPowerMode = options.getControl(); // only restore power mode if control is enabled
|
boolean restoreNormalPowerMode = options.getControl(); // only restore power mode if control is enabled
|
||||||
if (options.getShowTouches() || options.getStayAwake()) {
|
if (options.getShowTouches() || options.getStayAwake()) {
|
||||||
Settings settings = Device.getSettings();
|
|
||||||
if (options.getShowTouches()) {
|
if (options.getShowTouches()) {
|
||||||
try {
|
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
|
// If "show touches" was disabled, it must be disabled back on clean up
|
||||||
mustDisableShowTouchesOnCleanUp = !"1".equals(oldValue);
|
mustDisableShowTouchesOnCleanUp = !"1".equals(oldValue);
|
||||||
} catch (SettingsException e) {
|
} catch (SettingsException e) {
|
||||||
@ -34,7 +33,7 @@ public final class Server {
|
|||||||
if (options.getStayAwake()) {
|
if (options.getStayAwake()) {
|
||||||
int stayOn = BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB | BatteryManager.BATTERY_PLUGGED_WIRELESS;
|
int stayOn = BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB | BatteryManager.BATTERY_PLUGGED_WIRELESS;
|
||||||
try {
|
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 {
|
try {
|
||||||
restoreStayOn = Integer.parseInt(oldValue);
|
restoreStayOn = Integer.parseInt(oldValue);
|
||||||
if (restoreStayOn == stayOn) {
|
if (restoreStayOn == stayOn) {
|
||||||
@ -52,8 +51,8 @@ public final class Server {
|
|||||||
|
|
||||||
if (options.getCleanup()) {
|
if (options.getCleanup()) {
|
||||||
try {
|
try {
|
||||||
CleanUp.configure(options.getDisplayId(), restoreStayOn, mustDisableShowTouchesOnCleanUp, restoreNormalPowerMode,
|
CleanUp.configure(options.getInstalled(), options.getDisplayId(), restoreStayOn, mustDisableShowTouchesOnCleanUp,
|
||||||
options.getPowerOffScreenOnClose());
|
restoreNormalPowerMode, options.getPowerOffScreenOnClose());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Ln.e("Could not configure cleanup", e);
|
Ln.e("Could not configure cleanup", e);
|
||||||
}
|
}
|
||||||
@ -252,6 +251,10 @@ public final class Server {
|
|||||||
boolean powerOn = Boolean.parseBoolean(value);
|
boolean powerOn = Boolean.parseBoolean(value);
|
||||||
options.setPowerOn(powerOn);
|
options.setPowerOn(powerOn);
|
||||||
break;
|
break;
|
||||||
|
case "installed":
|
||||||
|
boolean installed = Boolean.parseBoolean(value);
|
||||||
|
options.setInstalled(installed);
|
||||||
|
break;
|
||||||
case "send_device_meta":
|
case "send_device_meta":
|
||||||
boolean sendDeviceMeta = Boolean.parseBoolean(value);
|
boolean sendDeviceMeta = Boolean.parseBoolean(value);
|
||||||
options.setSendDeviceMeta(sendDeviceMeta);
|
options.setSendDeviceMeta(sendDeviceMeta);
|
||||||
|
@ -7,16 +7,14 @@ import android.os.Build;
|
|||||||
|
|
||||||
import java.io.IOException;
|
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_SYSTEM = ContentProvider.TABLE_SYSTEM;
|
||||||
public static final String TABLE_SECURE = ContentProvider.TABLE_SECURE;
|
public static final String TABLE_SECURE = ContentProvider.TABLE_SECURE;
|
||||||
public static final String TABLE_GLOBAL = ContentProvider.TABLE_GLOBAL;
|
public static final String TABLE_GLOBAL = ContentProvider.TABLE_GLOBAL;
|
||||||
|
|
||||||
private final ServiceManager serviceManager;
|
private Settings() {
|
||||||
|
/* not instantiable */
|
||||||
public Settings(ServiceManager serviceManager) {
|
|
||||||
this.serviceManager = serviceManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void execSettingsPut(String table, String key, String value) throws SettingsException {
|
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) {
|
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
|
||||||
// on Android >= 12, it always fails: <https://github.com/Genymobile/scrcpy/issues/2788>
|
// 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);
|
return provider.getValue(table, key);
|
||||||
} catch (SettingsException e) {
|
} catch (SettingsException e) {
|
||||||
Ln.w("Could not get settings value via ContentProvider, fallback to settings process", 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);
|
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) {
|
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
|
||||||
// on Android >= 12, it always fails: <https://github.com/Genymobile/scrcpy/issues/2788>
|
// 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);
|
provider.putValue(table, key, value);
|
||||||
} catch (SettingsException e) {
|
} catch (SettingsException e) {
|
||||||
Ln.w("Could not put settings value via ContentProvider, fallback to settings process", 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);
|
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) {
|
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
|
||||||
// on Android >= 12, it always fails: <https://github.com/Genymobile/scrcpy/issues/2788>
|
// 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);
|
String oldValue = provider.getValue(table, key);
|
||||||
if (!value.equals(oldValue)) {
|
if (!value.equals(oldValue)) {
|
||||||
provider.putValue(table, key, value);
|
provider.putValue(table, key, value);
|
||||||
|
@ -1,8 +1,16 @@
|
|||||||
package com.genymobile.scrcpy.wrappers;
|
package com.genymobile.scrcpy.wrappers;
|
||||||
|
|
||||||
|
import com.genymobile.scrcpy.Command;
|
||||||
import com.genymobile.scrcpy.DisplayInfo;
|
import com.genymobile.scrcpy.DisplayInfo;
|
||||||
|
import com.genymobile.scrcpy.Ln;
|
||||||
import com.genymobile.scrcpy.Size;
|
import com.genymobile.scrcpy.Size;
|
||||||
|
|
||||||
|
import android.view.Display;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public final class DisplayManager {
|
public final class DisplayManager {
|
||||||
private final Object manager; // instance of hidden class android.hardware.display.DisplayManagerGlobal
|
private final Object manager; // instance of hidden class android.hardware.display.DisplayManagerGlobal
|
||||||
|
|
||||||
@ -10,11 +18,61 @@ public final class DisplayManager {
|
|||||||
this.manager = 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) {
|
public DisplayInfo getDisplayInfo(int displayId) {
|
||||||
try {
|
try {
|
||||||
Object displayInfo = manager.getClass().getMethod("getDisplayInfo", int.class).invoke(manager, displayId);
|
Object displayInfo = manager.getClass().getMethod("getDisplayInfo", int.class).invoke(manager, displayId);
|
||||||
if (displayInfo == null) {
|
if (displayInfo == null) {
|
||||||
return null;
|
// fallback when displayInfo is null
|
||||||
|
return getDisplayInfoFromDumpsysDisplay(displayId);
|
||||||
}
|
}
|
||||||
Class<?> cls = displayInfo.getClass();
|
Class<?> cls = displayInfo.getClass();
|
||||||
// width and height already take the rotation into account
|
// width and height already take the rotation into account
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
package com.genymobile.scrcpy.wrappers;
|
||||||
|
|
||||||
|
import com.genymobile.scrcpy.Ln;
|
||||||
|
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
|
import android.os.IInterface;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
public class PackageManager {
|
||||||
|
|
||||||
|
private IInterface manager;
|
||||||
|
private Method getApplicationInfoMethod;
|
||||||
|
|
||||||
|
public PackageManager(IInterface manager) {
|
||||||
|
this.manager = manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Method getGetApplicationInfoMethod() throws NoSuchMethodException {
|
||||||
|
if (getApplicationInfoMethod == null) {
|
||||||
|
getApplicationInfoMethod = manager.getClass().getDeclaredMethod("getApplicationInfo", String.class, int.class, int.class);
|
||||||
|
}
|
||||||
|
return getApplicationInfoMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApplicationInfo getApplicationInfo(String packageName) {
|
||||||
|
try {
|
||||||
|
Method method = getGetApplicationInfoMethod();
|
||||||
|
return (ApplicationInfo) method.invoke(manager, packageName, 0, ServiceManager.USER_ID);
|
||||||
|
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
|
||||||
|
Ln.e("Cannot get application info", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -13,27 +13,31 @@ public final class ServiceManager {
|
|||||||
public static final String PACKAGE_NAME = "com.android.shell";
|
public static final String PACKAGE_NAME = "com.android.shell";
|
||||||
public static final int USER_ID = 0;
|
public static final int USER_ID = 0;
|
||||||
|
|
||||||
private final Method getServiceMethod;
|
private static final Method GET_SERVICE_METHOD;
|
||||||
|
static {
|
||||||
private WindowManager windowManager;
|
|
||||||
private DisplayManager displayManager;
|
|
||||||
private InputManager inputManager;
|
|
||||||
private PowerManager powerManager;
|
|
||||||
private StatusBarManager statusBarManager;
|
|
||||||
private ClipboardManager clipboardManager;
|
|
||||||
private ActivityManager activityManager;
|
|
||||||
|
|
||||||
public ServiceManager() {
|
|
||||||
try {
|
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) {
|
} catch (Exception e) {
|
||||||
throw new AssertionError(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 static PackageManager packageManager;
|
||||||
|
|
||||||
|
private ServiceManager() {
|
||||||
|
/* not instantiable */
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IInterface getService(String service, String type) {
|
||||||
try {
|
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);
|
Method asInterfaceMethod = Class.forName(type + "$Stub").getMethod("asInterface", IBinder.class);
|
||||||
return (IInterface) asInterfaceMethod.invoke(null, binder);
|
return (IInterface) asInterfaceMethod.invoke(null, binder);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -41,14 +45,14 @@ public final class ServiceManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public WindowManager getWindowManager() {
|
public static WindowManager getWindowManager() {
|
||||||
if (windowManager == null) {
|
if (windowManager == null) {
|
||||||
windowManager = new WindowManager(getService("window", "android.view.IWindowManager"));
|
windowManager = new WindowManager(getService("window", "android.view.IWindowManager"));
|
||||||
}
|
}
|
||||||
return windowManager;
|
return windowManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DisplayManager getDisplayManager() {
|
public static DisplayManager getDisplayManager() {
|
||||||
if (displayManager == null) {
|
if (displayManager == null) {
|
||||||
try {
|
try {
|
||||||
Class<?> clazz = Class.forName("android.hardware.display.DisplayManagerGlobal");
|
Class<?> clazz = Class.forName("android.hardware.display.DisplayManagerGlobal");
|
||||||
@ -62,7 +66,7 @@ public final class ServiceManager {
|
|||||||
return displayManager;
|
return displayManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public InputManager getInputManager() {
|
public static InputManager getInputManager() {
|
||||||
if (inputManager == null) {
|
if (inputManager == null) {
|
||||||
try {
|
try {
|
||||||
Method getInstanceMethod = android.hardware.input.InputManager.class.getDeclaredMethod("getInstance");
|
Method getInstanceMethod = android.hardware.input.InputManager.class.getDeclaredMethod("getInstance");
|
||||||
@ -75,21 +79,21 @@ public final class ServiceManager {
|
|||||||
return inputManager;
|
return inputManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PowerManager getPowerManager() {
|
public static PowerManager getPowerManager() {
|
||||||
if (powerManager == null) {
|
if (powerManager == null) {
|
||||||
powerManager = new PowerManager(getService("power", "android.os.IPowerManager"));
|
powerManager = new PowerManager(getService("power", "android.os.IPowerManager"));
|
||||||
}
|
}
|
||||||
return powerManager;
|
return powerManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StatusBarManager getStatusBarManager() {
|
public static StatusBarManager getStatusBarManager() {
|
||||||
if (statusBarManager == null) {
|
if (statusBarManager == null) {
|
||||||
statusBarManager = new StatusBarManager(getService("statusbar", "com.android.internal.statusbar.IStatusBarService"));
|
statusBarManager = new StatusBarManager(getService("statusbar", "com.android.internal.statusbar.IStatusBarService"));
|
||||||
}
|
}
|
||||||
return statusBarManager;
|
return statusBarManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ClipboardManager getClipboardManager() {
|
public static ClipboardManager getClipboardManager() {
|
||||||
if (clipboardManager == null) {
|
if (clipboardManager == null) {
|
||||||
IInterface clipboard = getService("clipboard", "android.content.IClipboard");
|
IInterface clipboard = getService("clipboard", "android.content.IClipboard");
|
||||||
if (clipboard == null) {
|
if (clipboard == null) {
|
||||||
@ -103,7 +107,7 @@ public final class ServiceManager {
|
|||||||
return clipboardManager;
|
return clipboardManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ActivityManager getActivityManager() {
|
public static ActivityManager getActivityManager() {
|
||||||
if (activityManager == null) {
|
if (activityManager == null) {
|
||||||
try {
|
try {
|
||||||
// On old Android versions, the ActivityManager is not exposed via AIDL,
|
// On old Android versions, the ActivityManager is not exposed via AIDL,
|
||||||
@ -119,4 +123,20 @@ public final class ServiceManager {
|
|||||||
|
|
||||||
return activityManager;
|
return activityManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static PackageManager getPackageManager() {
|
||||||
|
if (packageManager == null) {
|
||||||
|
try {
|
||||||
|
//IInterface manager = getService("package", "android.content.pm.IPackageManager");
|
||||||
|
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
|
||||||
|
Method getPackageManager = activityThreadClass.getDeclaredMethod("getPackageManager");
|
||||||
|
IInterface manager = (IInterface) getPackageManager.invoke(null);
|
||||||
|
return new PackageManager(manager);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return packageManager;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
42
server/src/test/java/com/genymobile/scrcpy/BinaryTest.java
Normal file
42
server/src/test/java/com/genymobile/scrcpy/BinaryTest.java
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class BinaryTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testU16FixedPointToFloat() {
|
||||||
|
final float delta = 0.0f; // on these values, there MUST be no rounding error
|
||||||
|
Assert.assertEquals(0.0f, Binary.u16FixedPointToFloat((short) 0), delta);
|
||||||
|
Assert.assertEquals(0.03125f, Binary.u16FixedPointToFloat((short) 0x800), delta);
|
||||||
|
Assert.assertEquals(0.0625f, Binary.u16FixedPointToFloat((short) 0x1000), delta);
|
||||||
|
Assert.assertEquals(0.125f, Binary.u16FixedPointToFloat((short) 0x2000), delta);
|
||||||
|
Assert.assertEquals(0.25f, Binary.u16FixedPointToFloat((short) 0x4000), delta);
|
||||||
|
Assert.assertEquals(0.5f, Binary.u16FixedPointToFloat((short) 0x8000), delta);
|
||||||
|
Assert.assertEquals(0.75f, Binary.u16FixedPointToFloat((short) 0xc000), delta);
|
||||||
|
Assert.assertEquals(1.0f, Binary.u16FixedPointToFloat((short) 0xffff), delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testI16FixedPointToFloat() {
|
||||||
|
final float delta = 0.0f; // on these values, there MUST be no rounding error
|
||||||
|
|
||||||
|
Assert.assertEquals(0.0f, Binary.i16FixedPointToFloat((short) 0), delta);
|
||||||
|
Assert.assertEquals(0.03125f, Binary.i16FixedPointToFloat((short) 0x400), delta);
|
||||||
|
Assert.assertEquals(0.0625f, Binary.i16FixedPointToFloat((short) 0x800), delta);
|
||||||
|
Assert.assertEquals(0.125f, Binary.i16FixedPointToFloat((short) 0x1000), delta);
|
||||||
|
Assert.assertEquals(0.25f, Binary.i16FixedPointToFloat((short) 0x2000), delta);
|
||||||
|
Assert.assertEquals(0.5f, Binary.i16FixedPointToFloat((short) 0x4000), delta);
|
||||||
|
Assert.assertEquals(0.75f, Binary.i16FixedPointToFloat((short) 0x6000), delta);
|
||||||
|
Assert.assertEquals(1.0f, Binary.i16FixedPointToFloat((short) 0x7fff), delta);
|
||||||
|
|
||||||
|
Assert.assertEquals(-0.03125f, Binary.i16FixedPointToFloat((short) -0x400), delta);
|
||||||
|
Assert.assertEquals(-0.0625f, Binary.i16FixedPointToFloat((short) -0x800), delta);
|
||||||
|
Assert.assertEquals(-0.125f, Binary.i16FixedPointToFloat((short) -0x1000), delta);
|
||||||
|
Assert.assertEquals(-0.25f, Binary.i16FixedPointToFloat((short) -0x2000), delta);
|
||||||
|
Assert.assertEquals(-0.5f, Binary.i16FixedPointToFloat((short) -0x4000), delta);
|
||||||
|
Assert.assertEquals(-0.75f, Binary.i16FixedPointToFloat((short) -0x6000), delta);
|
||||||
|
Assert.assertEquals(-1.0f, Binary.i16FixedPointToFloat((short) -0x8000), delta);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,186 @@
|
|||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
@ -126,8 +126,8 @@ public class ControlMessageReaderTest {
|
|||||||
dos.writeInt(1026);
|
dos.writeInt(1026);
|
||||||
dos.writeShort(1080);
|
dos.writeShort(1080);
|
||||||
dos.writeShort(1920);
|
dos.writeShort(1920);
|
||||||
dos.writeInt(1);
|
dos.writeShort(0); // 0.0f encoded as i16
|
||||||
dos.writeInt(-1);
|
dos.writeShort(0x8000); // -1.0f encoded as i16
|
||||||
dos.writeInt(1);
|
dos.writeInt(1);
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
@ -143,8 +143,8 @@ public class ControlMessageReaderTest {
|
|||||||
Assert.assertEquals(1026, event.getPosition().getPoint().getY());
|
Assert.assertEquals(1026, event.getPosition().getPoint().getY());
|
||||||
Assert.assertEquals(1080, event.getPosition().getScreenSize().getWidth());
|
Assert.assertEquals(1080, event.getPosition().getScreenSize().getWidth());
|
||||||
Assert.assertEquals(1920, event.getPosition().getScreenSize().getHeight());
|
Assert.assertEquals(1920, event.getPosition().getScreenSize().getHeight());
|
||||||
Assert.assertEquals(1, event.getHScroll());
|
Assert.assertEquals(0f, event.getHScroll(), 0f);
|
||||||
Assert.assertEquals(-1, event.getVScroll());
|
Assert.assertEquals(-1f, event.getVScroll(), 0f);
|
||||||
Assert.assertEquals(1, event.getButtons());
|
Assert.assertEquals(1, event.getButtons());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user