Compare commits

..

2 Commits

Author SHA1 Message Date
Simon Chan
3b6495aefc Use precise scrolling values
Since SDL 2.0.18, the amount scrolled horizontally or vertically is
exposed as a float (between 0 and 1). Forward a precise value to the
Android device when possible.

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

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-07-24 16:49:51 +02:00
Romain Vimont
72ff957e9a Extract conversion from fixed-point u16 to float
So that it can be reused for other fields.

PR #3369 <https://github.com/Genymobile/scrcpy/pull/3369>
2022-07-24 16:09:17 +02:00
26 changed files with 173 additions and 342 deletions

View File

@@ -245,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

@@ -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"
@@ -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,18 +109,19 @@ 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 = // map [-1, 1] to [0, 1], then to uint16
sc_float_to_i16fp(msg->inject_scroll_event.hscroll); uint16_t hscroll =
int16_t vscroll = to_fixed_point_16((msg->inject_scroll_event.hscroll + 1) / 2);
sc_float_to_i16fp(msg->inject_scroll_event.vscroll); uint16_t vscroll =
sc_write16be(&buf[13], (uint16_t) hscroll); to_fixed_point_16((msg->inject_scroll_event.vscroll + 1) / 2);
sc_write16be(&buf[15], (uint16_t) vscroll); sc_write16be(&buf[13], hscroll);
sc_write16be(&buf[15], vscroll);
sc_write32be(&buf[17], msg->inject_scroll_event.buttons); sc_write32be(&buf[17], msg->inject_scroll_event.buttons);
return 21; return 21;
case SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON: case SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
@@ -162,7 +173,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 +183,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 +194,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=%g"
" vscroll=%f buttons=%06lx", " vscroll=%g 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

@@ -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

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

@@ -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

@@ -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

View File

@@ -1,11 +1,11 @@
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"

View File

@@ -14,8 +14,8 @@ set -e
SCRCPY_DEBUG=false SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=1.24 SCRCPY_VERSION_NAME=1.24
PLATFORM=${ANDROID_PLATFORM:-33} PLATFORM=${ANDROID_PLATFORM:-31}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-33.0.0} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-31.0.0}
BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})" BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})"
CLASSES_DIR="$BUILD_DIR/classes" CLASSES_DIR="$BUILD_DIR/classes"

View File

@@ -13,8 +13,8 @@ if prebuilt_server == ''
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,

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

@@ -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

@@ -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,10 @@ public class ControlMessageReader {
if (buffer.remaining() < INJECT_TOUCH_EVENT_PAYLOAD_LENGTH) { if (buffer.remaining() < INJECT_TOUCH_EVENT_PAYLOAD_LENGTH) {
return null; return null;
} }
int action = 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()); float pressure = fixedPointToFloat(buffer.getShort());
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 +149,9 @@ public class ControlMessageReader {
return null; return null;
} }
Position position = readPosition(buffer); Position position = readPosition(buffer);
float hScroll = Binary.i16FixedPointToFloat(buffer.getShort()); // map from [0; 1] to [-1; 1]
float vScroll = Binary.i16FixedPointToFloat(buffer.getShort()); float hScroll = fixedPointToFloat(buffer.getShort());
float vScroll = fixedPointToFloat(buffer.getShort());
int buttons = buffer.getInt(); int buttons = buffer.getInt();
return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll, buttons); return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll, buttons);
} }
@@ -159,7 +160,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 +168,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 +196,22 @@ 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;
}
private static float fixedPointToFloat(short value) {
int unsignedShort = toUnsigned(value);
// convert 16 bits fixed-point to a float between 0 and 1 (0x1p16f is 2^16 as float)
return unsignedShort == 0xffff ? 1f : (unsignedShort / 0x1p16f);
}
} }

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

@@ -1,48 +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 java.util.regex.Matcher; import android.os.IInterface;
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;
} }
private DisplayInfo parseDumpsysDisplay(int displayId) {
try {
String dumpDisplay = Command.execReadOutput("dumpsys", "display");
Pattern regex = Pattern.compile(
"^ mBaseDisplayInfo=DisplayInfo\\{\".*\", displayId " + displayId + ".+, real ([0-9]+) x ([0-9]+).+, rotation ([0-9]+).*, " +
"layerStack ([0-9]+).*");
Matcher m = regex.matcher(dumpDisplay);
if (!m.find()) {
return null;
}
int width = Integer.parseInt(m.group(1));
int height = Integer.parseInt(m.group(2));
int rotation = Integer.parseInt(m.group(3));
int layerStack = Integer.parseInt(m.group(4));
int flags = 0; // TODO
return new DisplayInfo(displayId, new Size(width, height), rotation, layerStack, flags);
} catch (Exception e) {
Ln.e("Could not get display info from \"dumpsys display\" output", e);
return null;
}
}
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 parseDumpsysDisplay(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

@@ -50,14 +50,7 @@ public final class ServiceManager {
public 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;
} }

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

@@ -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.writeShort(0x8000);
dos.writeShort(0x8000); // -1.0f encoded as i16 dos.writeShort(0);
dos.writeInt(1); dos.writeInt(1);
byte[] packet = bos.toByteArray(); byte[] packet = bos.toByteArray();