Compare commits

..

2 Commits

Author SHA1 Message Date
652e210bfb cross-compile 2022-05-23 21:07:30 +02:00
a1117c58ac [WIP] build deps 2022-05-23 21:06:52 +02:00
63 changed files with 489 additions and 1288 deletions

View File

@ -1,13 +0,0 @@
[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

View File

@ -1,13 +0,0 @@
[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

2
app/deps/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/data
/target-*

64
app/deps/src/build-ffmpeg.sh Executable file
View File

@ -0,0 +1,64 @@
#!/usr/bin/env bash
. init_deps
VERSION=5.0.1
FILENAME=ffmpeg-$VERSION.tar.xz
URL=http://ffmpeg.org/releases/ffmpeg-$VERSION.tar.xz
SHA256SUM=ef2efae259ce80a240de48ec85ecb062cecca26e4352ffb3fda562c21a93007b
DEP_DIR="$DATA_DIR/ffmpeg-$VERSION-$SHA256SUM"
if [[ ! -d "$DEP_DIR" ]]
then
get_file "$URL" "$FILENAME" "$SHA256SUM"
mkdir "$DEP_DIR"
cd "$DEP_DIR"
tar xvf "../$FILENAME"
else
echo "$DEP_DIR found"
cd "$DEP_DIR"
fi
cd "ffmpeg-$VERSION"
rm -rf "build-$HOST"
mkdir "build-$HOST"
cd "build-$HOST"
params=(
--prefix="$INSTALL_DIR"
--arch="$ARCH"
--disable-autodetect
--disable-programs
--disable-everything
--disable-doc
--disable-swresample
--disable-swscale
--disable-avfilter
--disable-postproc
--disable-static
--enable-shared
--enable-decoder=h264
--enable-decoder=png
--enable-muxer=mp4
--enable-muxer=matroska
)
case "$HOST_SYSTEM" in
linux)
params+=(--enable-libv4l2)
;;
windows)
params+=(--target-os=mingw32)
params+=(--cross-prefix="$HOST-")
;;
*)
fail "Unsupported platform: $HOST"
;;
esac
../configure "${params[@]}"
make -j $NJOBS
make install

56
app/deps/src/init_deps Normal file
View File

@ -0,0 +1,56 @@
set -e
# The caller must set the following environment variable
# - $HOST (e.g. "x86_64-linux-gnu")
# - $HOST_SYSTEM ("linux", "windows", "apple"), for scripts convenience
fail() {
echo "$1" >&2
exit 1
}
[[ -z "$HOST" ]] && fail '$HOST not defined'
if [[ "$HOST" == *linux* ]]
then
HOST_SYSTEM='linux'
elif [[ "$HOST" == *mingw* ]]
then
HOST_SYSTEM='windows'
else
fail "Host system could not be deduced from '$HOST'"
fi
ARCH="${HOST%%-*}"
[[ -z "$ARCH" ]] && fail "Arch could not be deduced from '$HOST'"
DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DIR"
DATA_DIR=$(realpath ../data)
INSTALL_DIR=$(realpath ../target-"$HOST")
NJOBS=$(grep -c ^processor /proc/cpuinfo)
mkdir -p "$DATA_DIR"
cd "$DATA_DIR"
checksum() {
local file="$1"
local sum="$2"
echo "$file: verifying checksum..."
echo "$sum $file" | sha256sum -c
}
get_file() {
local url="$1"
local file="$2"
local sum="$3"
if [[ -f "$file" ]]
then
echo "$file: found"
else
echo "$file: not found, downloading..."
wget "$url" -O "$file"
fi
checksum "$file" "$sum"
}

View File

@ -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.apk accessible from the same # build a "portable" version (with scrcpy-server 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,26 +223,14 @@ 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: join_paths(datadir, 'icons/hicolor/256x256/apps')) install_dir: 'share/icons/hicolor/256x256/apps')
install_data('data/zsh-completion/_scrcpy', install_data('data/zsh-completion/_scrcpy',
install_dir: join_paths(datadir, 'zsh/site-functions')) install_dir: 'share/zsh/site-functions')
install_data('data/bash-completion/scrcpy', install_data('data/bash-completion/scrcpy',
install_dir: join_paths(datadir, 'bash-completion/completions')) install_dir: 'share/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
@ -257,8 +245,8 @@ if get_option('buildtype') == 'debug'
'src/util/str.c', 'src/util/str.c',
'src/util/strbuf.c', 'src/util/strbuf.c',
]], ]],
['test_binary', [ ['test_buffer_util', [
'tests/test_binary.c', 'tests/test_buffer_util.c',
]], ]],
['test_cbuf', [ ['test_cbuf', [
'tests/test_cbuf.c', 'tests/test_cbuf.c',

View File

@ -110,10 +110,6 @@ 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).
@ -246,10 +242,6 @@ 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).

View File

@ -329,17 +329,6 @@ 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) {
@ -412,7 +401,6 @@ sc_adb_list_devices(struct sc_intr *intr, unsigned flags,
#define BUFSIZE 65536 #define BUFSIZE 65536
char *buf = malloc(BUFSIZE); char *buf = malloc(BUFSIZE);
if (!buf) { if (!buf) {
LOG_OOM();
return false; return false;
} }
@ -446,7 +434,6 @@ 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';
@ -723,101 +710,5 @@ sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) {
// It is parsed as a NUL-terminated string // It is parsed as a NUL-terminated string
buf[r] = '\0'; buf[r] = '\0';
return sc_adb_parse_device_ip(buf); return sc_adb_parse_device_ip_from_output(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;
} }

View File

@ -15,8 +15,6 @@
#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);
@ -66,10 +64,6 @@ 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>`
*/ */
@ -120,18 +114,4 @@ 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

View File

@ -199,7 +199,7 @@ sc_adb_parse_device_ip_from_line(char *line) {
} }
char * char *
sc_adb_parse_device_ip(char *str) { sc_adb_parse_device_ip_from_output(char *str) {
size_t idx_line = 0; size_t idx_line = 0;
while (str[idx_line] != '\0') { while (str[idx_line] != '\0') {
char *line = &str[idx_line]; char *line = &str[idx_line];
@ -225,62 +225,3 @@ 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;
}

View File

@ -25,26 +25,6 @@ sc_adb_parse_devices(char *str, struct sc_vec_adb_devices *out_vec);
* Warning: this function modifies the buffer for optimization purposes. * Warning: this function modifies the buffer for optimization purposes.
*/ */
char * char *
sc_adb_parse_device_ip(char *str); sc_adb_parse_device_ip_from_output(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

View File

@ -57,8 +57,6 @@
#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;
@ -209,12 +207,6 @@ 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",
@ -386,13 +378,6 @@ 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",
@ -1625,13 +1610,6 @@ 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;

View File

@ -98,7 +98,7 @@ sc_clock_update(struct sc_clock *clock, sc_tick system, sc_tick stream) {
sc_clock_estimate(clock, &clock->slope, &clock->offset); sc_clock_estimate(clock, &clock->slope, &clock->offset);
#ifndef SC_CLOCK_NDEBUG #ifndef SC_CLOCK_NDEBUG
LOGD("Clock estimation: %f * pts + %" PRItick, LOGD("Clock estimation: %g * pts + %" PRItick,
clock->slope, clock->offset); clock->slope, clock->offset);
#endif #endif
} }

View File

@ -5,7 +5,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "util/binary.h" #include "util/buffer_util.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",
"pointer-down", "ponter-down",
"pointer-up", "pointer-up",
"hover-move", "hover-move",
"scroll", "scroll",
@ -78,6 +78,16 @@ 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;
@ -99,20 +109,18 @@ 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 =
sc_float_to_u16fp(msg->inject_touch_event.pressure); to_fixed_point_16(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);
int16_t hscroll = sc_write32be(&buf[13],
sc_float_to_i16fp(msg->inject_scroll_event.hscroll); (uint32_t) msg->inject_scroll_event.hscroll);
int16_t vscroll = sc_write32be(&buf[17],
sc_float_to_i16fp(msg->inject_scroll_event.vscroll); (uint32_t) msg->inject_scroll_event.vscroll);
sc_write16be(&buf[13], (uint16_t) hscroll); sc_write32be(&buf[21], msg->inject_scroll_event.buttons);
sc_write16be(&buf[15], (uint16_t) vscroll); return 25;
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;
@ -162,7 +170,7 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
if (id == POINTER_ID_MOUSE || id == POINTER_ID_VIRTUAL_FINGER) { if (id == POINTER_ID_MOUSE || id == POINTER_ID_VIRTUAL_FINGER) {
// string pointer id // string pointer id
LOG_CMSG("touch [id=%s] %-4s position=%" PRIi32 ",%" PRIi32 LOG_CMSG("touch [id=%s] %-4s position=%" PRIi32 ",%" PRIi32
" pressure=%f buttons=%06lx", " pressure=%g buttons=%06lx",
id == POINTER_ID_MOUSE ? "mouse" : "vfinger", id == POINTER_ID_MOUSE ? "mouse" : "vfinger",
MOTIONEVENT_ACTION_LABEL(action), MOTIONEVENT_ACTION_LABEL(action),
msg->inject_touch_event.position.point.x, msg->inject_touch_event.position.point.x,
@ -172,7 +180,7 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
} else { } else {
// numeric pointer id // numeric pointer id
LOG_CMSG("touch [id=%" PRIu64_ "] %-4s position=%" PRIi32 ",%" LOG_CMSG("touch [id=%" PRIu64_ "] %-4s position=%" PRIi32 ",%"
PRIi32 " pressure=%f buttons=%06lx", PRIi32 " pressure=%g buttons=%06lx",
id, id,
MOTIONEVENT_ACTION_LABEL(action), MOTIONEVENT_ACTION_LABEL(action),
msg->inject_touch_event.position.point.x, msg->inject_touch_event.position.point.x,
@ -183,8 +191,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=%f" LOG_CMSG("scroll position=%" PRIi32 ",%" PRIi32 " hscroll=%" PRIi32
" vscroll=%f buttons=%06lx", " vscroll=%" PRIi32 " 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,

View File

@ -68,8 +68,8 @@ struct sc_control_msg {
} inject_touch_event; } inject_touch_event;
struct { struct {
struct sc_position position; struct sc_position position;
float hscroll; int32_t hscroll;
float vscroll; int32_t vscroll;
enum android_motionevent_buttons buttons; enum android_motionevent_buttons buttons;
} inject_scroll_event; } inject_scroll_event;
struct { struct {

View File

@ -7,7 +7,7 @@
#include "decoder.h" #include "decoder.h"
#include "events.h" #include "events.h"
#include "recorder.h" #include "recorder.h"
#include "util/binary.h" #include "util/buffer_util.h"
#include "util/log.h" #include "util/log.h"
#define SC_PACKET_HEADER_SIZE 12 #define SC_PACKET_HEADER_SIZE 12
@ -37,8 +37,8 @@ sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
// CK...... ........ ........ ........ ........ ........ ........ ........ // CK...... ........ ........ ........ ........ ........ ........ ........
// ^^<-------------------------------------------------------------------> // ^^<------------------------------------------------------------------->
// || PTS // || PTS
// | `- key frame // | `- config packet
// `-- config packet // `-- key frame
uint8_t header[SC_PACKET_HEADER_SIZE]; uint8_t header[SC_PACKET_HEADER_SIZE];
ssize_t r = net_recv_all(demuxer->socket, header, SC_PACKET_HEADER_SIZE); ssize_t r = net_recv_all(demuxer->socket, header, SC_PACKET_HEADER_SIZE);

View File

@ -4,7 +4,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "util/binary.h" #include "util/buffer_util.h"
#include "util/log.h" #include "util/log.h"
ssize_t ssize_t

View File

@ -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;
float hscroll; int32_t hscroll;
float vscroll; int32_t vscroll;
uint8_t buttons_state; // bitwise-OR of sc_mouse_button values uint8_t buttons_state; // bitwise-OR of sc_mouse_button values
}; };

View File

@ -747,13 +747,8 @@ 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),
}, },
#if SDL_VERSION_ATLEAST(2, 0, 18) .hscroll = event->x,
.hscroll = CLAMP(event->preciseX, -1.0f, 1.0f), .vscroll = event->y,
.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),
}; };

View File

@ -65,6 +65,4 @@ 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,
}; };

View File

@ -140,8 +140,6 @@ 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;

View File

@ -325,8 +325,6 @@ 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 = {

View File

@ -14,11 +14,10 @@
#include "util/process_intr.h" #include "util/process_intr.h"
#include "util/str.h" #include "util/str.h"
#define SC_SERVER_FILENAME "scrcpy-server.apk" #define SC_SERVER_FILENAME "scrcpy-server"
#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.apk" #define SC_DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar"
static char * static char *
get_server_path(void) { get_server_path(void) {
@ -105,10 +104,7 @@ error:
} }
static bool static bool
push_server(struct sc_intr *intr, const char *serial, bool install, push_server(struct sc_intr *intr, const char *serial) {
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;
@ -118,28 +114,7 @@ push_server(struct sc_intr *intr, const char *serial, bool install,
free(server_path); free(server_path);
return false; return false;
} }
bool ok; bool ok = sc_adb_push(intr, serial, server_path, SC_DEVICE_SERVER_PATH, 0);
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;
} }
@ -177,38 +152,6 @@ 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) {
@ -217,20 +160,13 @@ 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; cmd[count++] = "CLASSPATH=" SC_DEVICE_SERVER_PATH;
cmd[count++] = "app_process"; cmd[count++] = "app_process";
#ifdef SERVER_DEBUGGER #ifdef SERVER_DEBUGGER
@ -316,10 +252,6 @@ 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
@ -340,8 +272,6 @@ 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]);
} }
@ -825,9 +755,8 @@ run_server(void *data) {
assert(serial); assert(serial);
LOGD("Device serial: %s", serial); LOGD("Device serial: %s", serial);
ok = push_server(&server->intr, serial, params->install, params->reinstall); ok = push_server(&server->intr, serial);
if (!ok) { if (!ok) {
LOGE("Failed to push server");
goto error_connection_failed; goto error_connection_failed;
} }

View File

@ -48,8 +48,6 @@ 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 {

51
app/src/stream.h Normal file
View File

@ -0,0 +1,51 @@
#ifndef STREAM_H
#define STREAM_H
#include "common.h"
#include <stdbool.h>
#include <stdint.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include "trait/packet_sink.h"
#include "util/net.h"
#include "util/thread.h"
#define STREAM_MAX_SINKS 2
struct stream {
sc_socket socket;
sc_thread thread;
struct sc_packet_sink *sinks[STREAM_MAX_SINKS];
unsigned sink_count;
AVCodecContext *codec_ctx;
AVCodecParserContext *parser;
// successive packets may need to be concatenated, until a non-config
// packet is available
AVPacket *pending;
const struct stream_callbacks *cbs;
void *cbs_userdata;
};
struct stream_callbacks {
void (*on_eos)(struct stream *stream, void *userdata);
};
void
stream_init(struct stream *stream, sc_socket socket,
const struct stream_callbacks *cbs, void *cbs_userdata);
void
stream_add_sink(struct stream *stream, struct sc_packet_sink *sink);
bool
stream_start(struct stream *stream);
void
stream_join(struct stream *stream);
#endif

View File

@ -92,14 +92,8 @@ 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) {
@ -108,12 +102,8 @@ 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) {
int devnull = open("/dev/null", O_WRONLY | O_CREAT, 0666); // Close stdout in the child process
if (devnull != -1) { close(STDOUT_FILENO);
dup2(devnull, STDOUT_FILENO);
} else {
LOGE("Could not open /dev/null for stdout");
}
} }
if (perr) { if (perr) {
@ -123,12 +113,8 @@ 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) {
int devnull = open("/dev/null", O_WRONLY | O_CREAT, 0666); // Close stderr in the child process
if (devnull != -1) { close(STDERR_FILENO);
dup2(devnull, STDERR_FILENO);
} else {
LOGE("Could not open /dev/null for stderr");
}
} }
close(internal[0]); close(internal[0]);

View File

@ -23,11 +23,6 @@ read_string(libusb_device_handle *handle, uint8_t desc_index) {
// When non-negative, 'result' contains the number of bytes written // When non-negative, 'result' contains the number of bytes written
char *s = malloc(result + 1); char *s = malloc(result + 1);
if (!s) {
LOG_OOM();
return NULL;
}
memcpy(s, buffer, result); memcpy(s, buffer, result);
s[result] = '\0'; s[result] = '\0';
return s; return s;

View File

@ -1,9 +1,8 @@
#ifndef SC_BINARY_H #ifndef SC_BUFFER_UTIL_H
#define SC_BINARY_H #define SC_BUFFER_UTIL_H
#include "common.h" #include "common.h"
#include <assert.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
@ -44,33 +43,4 @@ 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

View File

@ -3,10 +3,11 @@
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
#include <stdio.h> #include <stdio.h>
#include <SDL2/SDL_platform.h>
#include "log.h" #include "log.h"
#ifdef _WIN32 #ifdef __WINDOWS__
# include <ws2tcpip.h> # include <ws2tcpip.h>
typedef int socklen_t; typedef int socklen_t;
typedef SOCKET sc_raw_socket; typedef SOCKET sc_raw_socket;
@ -28,7 +29,7 @@
bool bool
net_init(void) { net_init(void) {
#ifdef _WIN32 #ifdef __WINDOWS__
WSADATA wsa; WSADATA wsa;
int res = WSAStartup(MAKEWORD(2, 2), &wsa) < 0; int res = WSAStartup(MAKEWORD(2, 2), &wsa) < 0;
if (res < 0) { if (res < 0) {
@ -41,14 +42,14 @@ net_init(void) {
void void
net_cleanup(void) { net_cleanup(void) {
#ifdef _WIN32 #ifdef __WINDOWS__
WSACleanup(); WSACleanup();
#endif #endif
} }
static inline sc_socket static inline sc_socket
wrap(sc_raw_socket sock) { wrap(sc_raw_socket sock) {
#ifdef _WIN32 #ifdef __WINDOWS__
if (sock == INVALID_SOCKET) { if (sock == INVALID_SOCKET) {
return SC_SOCKET_NONE; return SC_SOCKET_NONE;
} }
@ -71,7 +72,7 @@ wrap(sc_raw_socket sock) {
static inline sc_raw_socket static inline sc_raw_socket
unwrap(sc_socket socket) { unwrap(sc_socket socket) {
#ifdef _WIN32 #ifdef __WINDOWS__
if (socket == SC_SOCKET_NONE) { if (socket == SC_SOCKET_NONE) {
return INVALID_SOCKET; return INVALID_SOCKET;
} }
@ -159,8 +160,8 @@ net_connect(sc_socket socket, uint32_t addr, uint16_t port) {
} }
bool bool
net_listen(sc_socket server_socket, uint32_t addr, uint16_t port, int backlog) { net_listen(sc_socket socket, uint32_t addr, uint16_t port, int backlog) {
sc_raw_socket raw_sock = unwrap(server_socket); sc_raw_socket raw_sock = unwrap(socket);
int reuse = 1; int reuse = 1;
if (setsockopt(raw_sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse, if (setsockopt(raw_sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse,
@ -247,7 +248,7 @@ net_interrupt(sc_socket socket) {
sc_raw_socket raw_sock = unwrap(socket); sc_raw_socket raw_sock = unwrap(socket);
#ifdef _WIN32 #ifdef __WINDOWS__
if (!atomic_flag_test_and_set(&socket->closed)) { if (!atomic_flag_test_and_set(&socket->closed)) {
return !closesocket(raw_sock); return !closesocket(raw_sock);
} }
@ -261,7 +262,7 @@ bool
net_close(sc_socket socket) { net_close(sc_socket socket) {
sc_raw_socket raw_sock = unwrap(socket); sc_raw_socket raw_sock = unwrap(socket);
#ifdef _WIN32 #ifdef __WINDOWS__
bool ret = true; bool ret = true;
if (!atomic_flag_test_and_set(&socket->closed)) { if (!atomic_flag_test_and_set(&socket->closed)) {
ret = !closesocket(raw_sock); ret = !closesocket(raw_sock);

View File

@ -5,8 +5,9 @@
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <SDL2/SDL_platform.h>
#ifdef _WIN32 #ifdef __WINDOWS__
# include <winsock2.h> # include <winsock2.h>
# include <stdatomic.h> # include <stdatomic.h>
@ -16,7 +17,7 @@
atomic_flag closed; atomic_flag closed;
} *sc_socket; } *sc_socket;
#else // not _WIN32 #else // not __WINDOWS__
# include <sys/socket.h> # include <sys/socket.h>
# define SC_SOCKET_NONE -1 # define SC_SOCKET_NONE -1
@ -39,7 +40,7 @@ bool
net_connect(sc_socket socket, uint32_t addr, uint16_t port); net_connect(sc_socket socket, uint32_t addr, uint16_t port);
bool bool
net_listen(sc_socket server_socket, uint32_t addr, uint16_t port, int backlog); net_listen(sc_socket socket, uint32_t addr, uint16_t port, int backlog);
sc_socket sc_socket
net_accept(sc_socket server_socket); net_accept(sc_socket server_socket);

View File

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

View File

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

View File

@ -5,7 +5,7 @@
#include "adb/adb_device.h" #include "adb/adb_device.h"
#include "adb/adb_parser.h" #include "adb/adb_parser.h"
static void test_adb_devices(void) { static void test_adb_devices() {
char output[] = char output[] =
"List of devices attached\n" "List of devices attached\n"
"0123456789abcdef device usb:2-1 product:MyProduct model:MyModel " "0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
@ -31,7 +31,7 @@ static void test_adb_devices(void) {
sc_adb_devices_destroy(&vec); sc_adb_devices_destroy(&vec);
} }
static void test_adb_devices_cr(void) { static void test_adb_devices_cr() {
char output[] = char output[] =
"List of devices attached\r\n" "List of devices attached\r\n"
"0123456789abcdef device usb:2-1 product:MyProduct model:MyModel " "0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
@ -57,7 +57,7 @@ static void test_adb_devices_cr(void) {
sc_adb_devices_destroy(&vec); sc_adb_devices_destroy(&vec);
} }
static void test_adb_devices_daemon_start(void) { static void test_adb_devices_daemon_start() {
char output[] = char output[] =
"* daemon not running; starting now at tcp:5037\n" "* daemon not running; starting now at tcp:5037\n"
"* daemon started successfully\n" "* daemon started successfully\n"
@ -78,7 +78,7 @@ static void test_adb_devices_daemon_start(void) {
sc_adb_devices_destroy(&vec); sc_adb_devices_destroy(&vec);
} }
static void test_adb_devices_daemon_start_mixed(void) { static void test_adb_devices_daemon_start_mixed() {
char output[] = char output[] =
"List of devices attached\n" "List of devices attached\n"
"adb server version (41) doesn't match this client (39); killing...\n" "adb server version (41) doesn't match this client (39); killing...\n"
@ -105,7 +105,7 @@ static void test_adb_devices_daemon_start_mixed(void) {
sc_adb_devices_destroy(&vec); sc_adb_devices_destroy(&vec);
} }
static void test_adb_devices_without_eol(void) { static void test_adb_devices_without_eol() {
char output[] = char output[] =
"List of devices attached\n" "List of devices attached\n"
"0123456789abcdef device usb:2-1 product:MyProduct model:MyModel " "0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
@ -124,7 +124,7 @@ static void test_adb_devices_without_eol(void) {
sc_adb_devices_destroy(&vec); sc_adb_devices_destroy(&vec);
} }
static void test_adb_devices_without_header(void) { static void test_adb_devices_without_header() {
char output[] = char output[] =
"0123456789abcdef device usb:2-1 product:MyProduct model:MyModel " "0123456789abcdef device usb:2-1 product:MyProduct model:MyModel "
"device:MyDevice transport_id:1\n"; "device:MyDevice transport_id:1\n";
@ -134,7 +134,7 @@ static void test_adb_devices_without_header(void) {
assert(!ok); assert(!ok);
} }
static void test_adb_devices_corrupted(void) { static void test_adb_devices_corrupted() {
char output[] = char output[] =
"List of devices attached\n" "List of devices attached\n"
"corrupted_garbage\n"; "corrupted_garbage\n";
@ -145,7 +145,7 @@ static void test_adb_devices_corrupted(void) {
assert(vec.size == 0); assert(vec.size == 0);
} }
static void test_adb_devices_spaces(void) { static void test_adb_devices_spaces() {
char output[] = char output[] =
"List of devices attached\n" "List of devices attached\n"
"0123456789abcdef unauthorized usb:1-4 transport_id:3\n"; "0123456789abcdef unauthorized usb:1-4 transport_id:3\n";
@ -163,132 +163,84 @@ static void test_adb_devices_spaces(void) {
sc_adb_devices_destroy(&vec); sc_adb_devices_destroy(&vec);
} }
static void test_get_ip_single_line(void) { static void test_get_ip_single_line() {
char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src " char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src "
"192.168.12.34\r\r\n"; "192.168.12.34\r\r\n";
char *ip = sc_adb_parse_device_ip(ip_route); char *ip = sc_adb_parse_device_ip_from_output(ip_route);
assert(ip); assert(ip);
assert(!strcmp(ip, "192.168.12.34")); assert(!strcmp(ip, "192.168.12.34"));
free(ip); free(ip);
} }
static void test_get_ip_single_line_without_eol(void) { static void test_get_ip_single_line_without_eol() {
char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src " char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src "
"192.168.12.34"; "192.168.12.34";
char *ip = sc_adb_parse_device_ip(ip_route); char *ip = sc_adb_parse_device_ip_from_output(ip_route);
assert(ip); assert(ip);
assert(!strcmp(ip, "192.168.12.34")); assert(!strcmp(ip, "192.168.12.34"));
free(ip); free(ip);
} }
static void test_get_ip_single_line_with_trailing_space(void) { static void test_get_ip_single_line_with_trailing_space() {
char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src " char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src "
"192.168.12.34 \n"; "192.168.12.34 \n";
char *ip = sc_adb_parse_device_ip(ip_route); char *ip = sc_adb_parse_device_ip_from_output(ip_route);
assert(ip); assert(ip);
assert(!strcmp(ip, "192.168.12.34")); assert(!strcmp(ip, "192.168.12.34"));
free(ip); free(ip);
} }
static void test_get_ip_multiline_first_ok(void) { static void test_get_ip_multiline_first_ok() {
char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src " char ip_route[] = "192.168.1.0/24 dev wlan0 proto kernel scope link src "
"192.168.1.2\r\n" "192.168.1.2\r\n"
"10.0.0.0/24 dev rmnet proto kernel scope link src " "10.0.0.0/24 dev rmnet proto kernel scope link src "
"10.0.0.2\r\n"; "10.0.0.2\r\n";
char *ip = sc_adb_parse_device_ip(ip_route); char *ip = sc_adb_parse_device_ip_from_output(ip_route);
assert(ip); assert(ip);
assert(!strcmp(ip, "192.168.1.2")); assert(!strcmp(ip, "192.168.1.2"));
free(ip); free(ip);
} }
static void test_get_ip_multiline_second_ok(void) { static void test_get_ip_multiline_second_ok() {
char ip_route[] = "10.0.0.0/24 dev rmnet proto kernel scope link src " char ip_route[] = "10.0.0.0/24 dev rmnet proto kernel scope link src "
"10.0.0.3\r\n" "10.0.0.3\r\n"
"192.168.1.0/24 dev wlan0 proto kernel scope link src " "192.168.1.0/24 dev wlan0 proto kernel scope link src "
"192.168.1.3\r\n"; "192.168.1.3\r\n";
char *ip = sc_adb_parse_device_ip(ip_route); char *ip = sc_adb_parse_device_ip_from_output(ip_route);
assert(ip); assert(ip);
assert(!strcmp(ip, "192.168.1.3")); assert(!strcmp(ip, "192.168.1.3"));
free(ip); free(ip);
} }
static void test_get_ip_no_wlan(void) { static void test_get_ip_no_wlan() {
char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src " char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src "
"192.168.12.34\r\r\n"; "192.168.12.34\r\r\n";
char *ip = sc_adb_parse_device_ip(ip_route); char *ip = sc_adb_parse_device_ip_from_output(ip_route);
assert(!ip); assert(!ip);
} }
static void test_get_ip_no_wlan_without_eol(void) { static void test_get_ip_no_wlan_without_eol() {
char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src " char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src "
"192.168.12.34"; "192.168.12.34";
char *ip = sc_adb_parse_device_ip(ip_route); char *ip = sc_adb_parse_device_ip_from_output(ip_route);
assert(!ip); assert(!ip);
} }
static void test_get_ip_truncated(void) { static void test_get_ip_truncated() {
char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src " char ip_route[] = "192.168.1.0/24 dev rmnet proto kernel scope link src "
"\n"; "\n";
char *ip = sc_adb_parse_device_ip(ip_route); char *ip = sc_adb_parse_device_ip_from_output(ip_route);
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;
@ -310,10 +262,4 @@ int main(int argc, char *argv[]) {
test_get_ip_no_wlan(); test_get_ip_no_wlan();
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;
} }

View File

@ -1,114 +0,0 @@
#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;
}

View File

@ -0,0 +1,81 @@
#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;
}

View File

@ -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 == 21); assert(size == 25);
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
0x7F, 0xFF, // 1 (float encoded as i16) 0x00, 0x00, 0x00, 0x01, // 1
0x80, 0x00, // -1 (float encoded as i16) 0xFF, 0xFF, 0xFF, 0xFF, // -1
0x00, 0x00, 0x00, 0x01, // 1 0x00, 0x00, 0x00, 0x01, // 1
}; };
assert(!memcmp(buf, expected, sizeof(expected))); assert(!memcmp(buf, expected, sizeof(expected)));

View File

@ -4,10 +4,10 @@ buildscript {
repositories { repositories {
google() google()
mavenCentral() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.2.2' classpath 'com.android.tools.build:gradle:7.0.3'
// 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()
mavenCentral() jcenter()
} }
tasks.withType(JavaCompile) { tasks.withType(JavaCompile) {
options.compilerArgs << "-Xlint:deprecation" options.compilerArgs << "-Xlint:deprecation"

View File

@ -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.3.3-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

2
run
View File

@ -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.apk" \ SCRCPY_SERVER_PATH="$BUILDDIR/server/scrcpy-server" \
"$BUILDDIR/app/scrcpy" "$@" "$BUILDDIR/app/scrcpy" "$@"

1
server/.gitignore vendored
View File

@ -6,4 +6,3 @@
/build /build
/captures /captures
.externalNativeBuild .externalNativeBuild
/keystore.properties

View File

@ -1,12 +0,0 @@
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

View File

@ -1,40 +1,25 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
android { android {
compileSdkVersion 33 compileSdkVersion 31
defaultConfig { defaultConfig {
applicationId "com.genymobile.scrcpy" applicationId "com.genymobile.scrcpy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 33 targetSdkVersion 31
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
} }
} }
} }
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
testImplementation 'junit:junit:4.13.1' testImplementation 'junit:junit:4.13.1'
} }

View File

@ -14,42 +14,13 @@ set -e
SCRCPY_DEBUG=false SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=1.24 SCRCPY_VERSION_NAME=1.24
SERVER_DIR="$(realpath $(dirname "$0"))" PLATFORM=${ANDROID_PLATFORM:-31}
KEYSTORE_PROPERTIES_FILE="$SERVER_DIR/keystore.properties" BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-31.0.0}
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_BINARY=scrcpy-server.apk SERVER_DIR=$(dirname "$0")
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"
@ -70,8 +41,9 @@ EOF
echo "Generating java from aidl..." echo "Generating java from aidl..."
cd "$SERVER_DIR/src/main/aidl" cd "$SERVER_DIR/src/main/aidl"
"$BUILD_TOOLS_DIR/aidl" -o"$CLASSES_DIR" android/view/IRotationWatcher.aidl "$ANDROID_HOME/build-tools/$BUILD_TOOLS/aidl" -o"$CLASSES_DIR" \
"$BUILD_TOOLS_DIR/aidl" -o"$CLASSES_DIR" \ android/view/IRotationWatcher.aidl
"$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..."
@ -87,15 +59,20 @@ cd "$CLASSES_DIR"
if [[ $PLATFORM -lt 31 ]] if [[ $PLATFORM -lt 31 ]]
then then
# use dx # use dx
"$BUILD_TOOLS_DIR/dx" --dex --output "$BUILD_DIR/classes.dex" \ "$ANDROID_HOME/build-tools/$BUILD_TOOLS/dx" --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
"$BUILD_TOOLS_DIR/d8" --classpath "$ANDROID_JAR" \ "$ANDROID_HOME/build-tools/$BUILD_TOOLS/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 \
@ -103,24 +80,8 @@ else
com/genymobile/scrcpy/wrappers/*.class com/genymobile/scrcpy/wrappers/*.class
cd "$BUILD_DIR" cd "$BUILD_DIR"
unzip -o classes.zip classes.dex # we need the inner classes.dex mv classes.zip "$SERVER_BINARY"
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"

View File

@ -1,4 +0,0 @@
storeFile=/path/to/your/keystore
storePassword=PASSWORD
keyAlias=scrcpy
keyPassword=PASSWORD

View File

@ -6,19 +6,19 @@ 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.apk', output: 'scrcpy-server',
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,
install_dir: 'share/scrcpy') install_dir: 'share/scrcpy')
else else
if not prebuilt_server.startswith('/') if not prebuilt_server.startswith('/')
# prebuilt server path is relative to the root scrcpy directory # relative path needs some trick
prebuilt_server = '../' + prebuilt_server prebuilt_server = meson.source_root() + '/' + prebuilt_server
endif endif
custom_target('scrcpy-server-prebuilt', custom_target('scrcpy-server-prebuilt',
input: prebuilt_server, input: prebuilt_server,
output: 'scrcpy-server.apk', output: 'scrcpy-server',
command: ['cp', '@INPUT@', '@OUTPUT@'], command: ['cp', '@INPUT@', '@OUTPUT@'],
install: true, install: true,
install_dir: 'share/scrcpy') install_dir: 'share/scrcpy')

View File

@ -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.apk" "$OUTPUT" cp "$PROJECT_ROOT/build/outputs/apk/release/server-release-unsigned.apk" "$OUTPUT"
fi fi

View File

@ -1,38 +0,0 @@
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);
}
}

View File

@ -1,5 +1,7 @@
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;
@ -14,7 +16,7 @@ import java.io.IOException;
*/ */
public final class CleanUp { public final class CleanUp {
public static final String SERVER_PATH = "/data/local/tmp/scrcpy-server.apk"; public static final String SERVER_PATH = "/data/local/tmp/scrcpy-server.jar";
// A simple struct to be passed from the main process to the cleanup process // 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 {
@ -35,8 +37,6 @@ 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,7 +52,6 @@ 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();
@ -63,7 +62,6 @@ 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;
@ -118,10 +116,9 @@ public final class CleanUp {
// not instantiable // not instantiable
} }
public static void configure(boolean installed, int displayId, int restoreStayOn, boolean disableShowTouches, boolean restoreNormalPowerMode, public static void configure(int displayId, int restoreStayOn, boolean disableShowTouches, boolean restoreNormalPowerMode, boolean powerOffScreen)
boolean powerOffScreen) throws IOException { 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;
@ -130,9 +127,8 @@ public final class CleanUp {
if (config.hasWork()) { if (config.hasWork()) {
startProcess(config); startProcess(config);
} else if (!installed) { } else {
// 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();
} }
} }
@ -141,8 +137,7 @@ 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);
String serverPath = config.installed ? Device.getInstalledApkPath() : SERVER_PATH; builder.environment().put("CLASSPATH", SERVER_PATH);
builder.environment().put("CLASSPATH", serverPath);
builder.start(); builder.start();
} }
@ -155,12 +150,7 @@ public final class CleanUp {
} }
public static void main(String... args) { public static void main(String... args) {
Config config = Config.fromBase64(args[0]);
if (!config.installed) {
// If the APK has been pushed to /data/local/tmp, remove it.
unlinkSelf(); unlinkSelf();
}
try { try {
// Wait for the server to die // Wait for the server to die
@ -171,11 +161,15 @@ 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);
} }
@ -183,7 +177,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);
} }

View File

@ -30,14 +30,4 @@ 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;
}
} }

View File

@ -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 float hScroll; private int hScroll;
private float vScroll; private int 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, float hScroll, float vScroll, int buttons) { public static ControlMessage createInjectScrollEvent(Position position, int hScroll, int 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 float getHScroll() { public int getHScroll() {
return hScroll; return hScroll;
} }
public float getVScroll() { public int getVScroll() {
return vScroll; return vScroll;
} }

View File

@ -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 = 20; static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 24;
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 = Binary.toUnsigned(buffer.get()); int action = 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,10 +136,13 @@ 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 = Binary.toUnsigned(buffer.get()); int action = toUnsigned(buffer.get());
long pointerId = buffer.getLong(); long pointerId = buffer.getLong();
Position position = readPosition(buffer); Position position = readPosition(buffer);
float pressure = Binary.u16FixedPointToFloat(buffer.getShort()); // 16 bits fixed-point
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);
} }
@ -149,8 +152,8 @@ public class ControlMessageReader {
return null; return null;
} }
Position position = readPosition(buffer); Position position = readPosition(buffer);
float hScroll = Binary.i16FixedPointToFloat(buffer.getShort()); int hScroll = buffer.getInt();
float vScroll = Binary.i16FixedPointToFloat(buffer.getShort()); int vScroll = buffer.getInt();
int buttons = buffer.getInt(); int buttons = buffer.getInt();
return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll, buttons); return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll, buttons);
} }
@ -159,7 +162,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 = Binary.toUnsigned(buffer.get()); int action = toUnsigned(buffer.get());
return ControlMessage.createBackOrScreenOn(action); return ControlMessage.createBackOrScreenOn(action);
} }
@ -167,7 +170,7 @@ public class ControlMessageReader {
if (buffer.remaining() < GET_CLIPBOARD_LENGTH) { if (buffer.remaining() < GET_CLIPBOARD_LENGTH) {
return null; return null;
} }
int copyKey = Binary.toUnsigned(buffer.get()); int copyKey = toUnsigned(buffer.get());
return ControlMessage.createGetClipboard(copyKey); return ControlMessage.createGetClipboard(copyKey);
} }
@ -195,8 +198,16 @@ 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 = Binary.toUnsigned(buffer.getShort()); int screenWidth = toUnsigned(buffer.getShort());
int screenHeight = Binary.toUnsigned(buffer.getShort()); int screenHeight = 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;
}
} }

View File

@ -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, float hScroll, float vScroll, int buttons) { private boolean injectScroll(Position position, int hScroll, int 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) {

View File

@ -7,7 +7,6 @@ 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;
@ -22,8 +21,6 @@ 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;
@ -34,6 +31,9 @@ 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 = ServiceManager.getDisplayManager().getDisplayInfo(displayId); DisplayInfo displayInfo = SERVICE_MANAGER.getDisplayManager().getDisplayInfo(displayId);
if (displayInfo == null) { if (displayInfo == null) {
int[] displayIds = ServiceManager.getDisplayManager().getDisplayIds(); int[] displayIds = SERVICE_MANAGER.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();
ServiceManager.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() { SERVICE_MANAGER.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 = ServiceManager.getClipboardManager(); ClipboardManager clipboardManager = SERVICE_MANAGER.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 ServiceManager.getInputManager().injectInputEvent(inputEvent, injectMode); return SERVICE_MANAGER.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 ServiceManager.getPowerManager().isScreenOn(); return SERVICE_MANAGER.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() {
ServiceManager.getStatusBarManager().expandNotificationsPanel(); SERVICE_MANAGER.getStatusBarManager().expandNotificationsPanel();
} }
public static void expandSettingsPanel() { public static void expandSettingsPanel() {
ServiceManager.getStatusBarManager().expandSettingsPanel(); SERVICE_MANAGER.getStatusBarManager().expandSettingsPanel();
} }
public static void collapsePanels() { public static void collapsePanels() {
ServiceManager.getStatusBarManager().collapsePanels(); SERVICE_MANAGER.getStatusBarManager().collapsePanels();
} }
public static String getClipboardText() { public static String getClipboardText() {
ClipboardManager clipboardManager = ServiceManager.getClipboardManager(); ClipboardManager clipboardManager = SERVICE_MANAGER.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 = ServiceManager.getClipboardManager(); ClipboardManager clipboardManager = SERVICE_MANAGER.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 = ServiceManager.getWindowManager(); WindowManager wm = SERVICE_MANAGER.getWindowManager();
boolean accelerometerRotation = !wm.isRotationFrozen(); boolean accelerometerRotation = !wm.isRotationFrozen();
@ -316,11 +316,7 @@ public final class Device {
} }
} }
public static String getInstalledApkPath() { public static Settings getSettings() {
ApplicationInfo info = ServiceManager.getPackageManager().getApplicationInfo(SCRCPY_PACKAGE_NAME); return SETTINGS;
if (info == null) {
return null;
}
return info.sourceDir;
} }
} }

View File

@ -6,9 +6,7 @@ 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() {
@ -39,13 +37,4 @@ 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();
}
} }

View File

@ -23,7 +23,6 @@ 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
@ -197,12 +196,4 @@ 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;
}
} }

View File

@ -20,9 +20,10 @@ 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) {
@ -33,7 +34,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) {
@ -51,8 +52,8 @@ public final class Server {
if (options.getCleanup()) { if (options.getCleanup()) {
try { try {
CleanUp.configure(options.getInstalled(), options.getDisplayId(), restoreStayOn, mustDisableShowTouchesOnCleanUp, CleanUp.configure(options.getDisplayId(), restoreStayOn, mustDisableShowTouchesOnCleanUp, restoreNormalPowerMode,
restoreNormalPowerMode, options.getPowerOffScreenOnClose()); options.getPowerOffScreenOnClose());
} catch (IOException e) { } catch (IOException e) {
Ln.e("Could not configure cleanup", e); Ln.e("Could not configure cleanup", e);
} }
@ -251,10 +252,6 @@ 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);

View File

@ -7,14 +7,16 @@ import android.os.Build;
import java.io.IOException; import java.io.IOException;
public final class Settings { public 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 Settings() { private final ServiceManager serviceManager;
/* 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 {
@ -33,10 +35,10 @@ public final class Settings {
} }
} }
public static String getValue(String table, String key) throws SettingsException { public 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);
@ -46,10 +48,10 @@ public final class Settings {
return execSettingsGet(table, key); return execSettingsGet(table, key);
} }
public static void putValue(String table, String key, String value) throws SettingsException { public 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);
@ -59,10 +61,10 @@ public final class Settings {
execSettingsPut(table, key, value); execSettingsPut(table, key, value);
} }
public static String getAndPutValue(String table, String key, String value) throws SettingsException { public 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);

View File

@ -1,78 +1,22 @@
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 android.os.IInterface;
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 IInterface manager;
public DisplayManager(Object manager) { public DisplayManager(IInterface manager) {
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) {
// fallback when displayInfo is null return 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

View File

@ -1,36 +0,0 @@
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;
}
}
}

View File

@ -13,31 +13,27 @@ 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 static final Method GET_SERVICE_METHOD; private final Method getServiceMethod;
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 {
GET_SERVICE_METHOD = Class.forName("android.os.ServiceManager").getDeclaredMethod("getService", String.class); getServiceMethod = Class.forName("android.os.ServiceManager").getDeclaredMethod("getService", String.class);
} catch (Exception e) { } catch (Exception e) {
throw new AssertionError(e); throw new AssertionError(e);
} }
} }
private static WindowManager windowManager; private IInterface getService(String service, String type) {
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) GET_SERVICE_METHOD.invoke(null, service); IBinder binder = (IBinder) getServiceMethod.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) {
@ -45,28 +41,21 @@ public final class ServiceManager {
} }
} }
public static WindowManager getWindowManager() { public 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 static DisplayManager getDisplayManager() { public DisplayManager getDisplayManager() {
if (displayManager == null) { if (displayManager == null) {
try { displayManager = new DisplayManager(getService("display", "android.hardware.display.IDisplayManager"));
Class<?> clazz = Class.forName("android.hardware.display.DisplayManagerGlobal");
Method getInstanceMethod = clazz.getDeclaredMethod("getInstance");
Object dmg = getInstanceMethod.invoke(null);
displayManager = new DisplayManager(dmg);
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new AssertionError(e);
}
} }
return displayManager; return displayManager;
} }
public static InputManager getInputManager() { public 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");
@ -79,21 +68,21 @@ public final class ServiceManager {
return inputManager; return inputManager;
} }
public static PowerManager getPowerManager() { public 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 static StatusBarManager getStatusBarManager() { public 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 static ClipboardManager getClipboardManager() { public 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) {
@ -107,7 +96,7 @@ public final class ServiceManager {
return clipboardManager; return clipboardManager;
} }
public static ActivityManager getActivityManager() { public 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,
@ -123,20 +112,4 @@ 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;
}
} }

View File

@ -1,42 +0,0 @@
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);
}
}

View File

@ -1,186 +0,0 @@
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());
}
}

View File

@ -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.writeShort(0); // 0.0f encoded as i16 dos.writeInt(1);
dos.writeShort(0x8000); // -1.0f encoded as i16 dos.writeInt(-1);
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(0f, event.getHScroll(), 0f); Assert.assertEquals(1, event.getHScroll());
Assert.assertEquals(-1f, event.getVScroll(), 0f); Assert.assertEquals(-1, event.getVScroll());
Assert.assertEquals(1, event.getButtons()); Assert.assertEquals(1, event.getButtons());
} }