Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db85211653 | ||
|
|
c0e20c86d6 |
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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',
|
||||||
|
|||||||
@@ -401,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,6 +434,7 @@ sc_adb_list_devices(struct sc_intr *intr, unsigned flags,
|
|||||||
"Please report an issue.");
|
"Please report an issue.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
#undef BUFSIZE
|
||||||
|
|
||||||
// It is parsed as a NUL-terminated string
|
// It is parsed as a NUL-terminated string
|
||||||
buf[r] = '\0';
|
buf[r] = '\0';
|
||||||
@@ -713,3 +713,95 @@ sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) {
|
|||||||
|
|
||||||
return sc_adb_parse_device_ip(buf);
|
return sc_adb_parse_device_ip(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
sc_adb_get_installed_apk_path(struct sc_intr *intr, const char *serial,
|
||||||
|
unsigned flags) {
|
||||||
|
assert(serial);
|
||||||
|
const char *const argv[] =
|
||||||
|
SC_ADB_COMMAND("-s", serial, "shell", "pm", "list", "package", "-f",
|
||||||
|
SC_ANDROID_PACKAGE);
|
||||||
|
|
||||||
|
sc_pipe pout;
|
||||||
|
sc_pid pid = sc_adb_execute_p(argv, flags, &pout);
|
||||||
|
if (pid == SC_PROCESS_NONE) {
|
||||||
|
LOGD("Could not execute \"pm list packages\"");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// "pm list packages -f <package>" output should contain only one line, so
|
||||||
|
// the output should be short
|
||||||
|
char buf[1024];
|
||||||
|
ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf) - 1);
|
||||||
|
sc_pipe_close(pout);
|
||||||
|
|
||||||
|
bool ok = process_check_success_intr(intr, pid, "pm list packages", flags);
|
||||||
|
if (!ok) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r == -1) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert((size_t) r < sizeof(buf));
|
||||||
|
if (r == sizeof(buf) - 1) {
|
||||||
|
// The implementation assumes that the output of "pm list packages"
|
||||||
|
// fits in the buffer in a single pass
|
||||||
|
LOGW("Result of \"pm list package\" does not fit in 1Kb. "
|
||||||
|
"Please report an issue.");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// It is parsed as a NUL-terminated string
|
||||||
|
buf[r] = '\0';
|
||||||
|
|
||||||
|
return sc_adb_parse_installed_apk_path(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
sc_adb_get_installed_apk_version(struct sc_intr *intr, const char *serial,
|
||||||
|
unsigned flags) {
|
||||||
|
assert(serial);
|
||||||
|
const char *const argv[] =
|
||||||
|
SC_ADB_COMMAND("-s", serial, "shell", "dumpsys", "package",
|
||||||
|
SC_ANDROID_PACKAGE);
|
||||||
|
|
||||||
|
sc_pipe pout;
|
||||||
|
sc_pid pid = sc_adb_execute_p(argv, flags, &pout);
|
||||||
|
if (pid == SC_PROCESS_NONE) {
|
||||||
|
LOGD("Could not execute \"dumpsys package\"");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// "dumpsys package" output can be huge (e.g. 16k), but versionName is at
|
||||||
|
// the beginning, typically in the first 1024 bytes (64k should be enough
|
||||||
|
// for the whole output anyway)
|
||||||
|
#define BUFSIZE 65536
|
||||||
|
char *buf = malloc(BUFSIZE);
|
||||||
|
if (!buf) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, BUFSIZE - 1);
|
||||||
|
sc_pipe_close(pout);
|
||||||
|
|
||||||
|
bool ok = process_check_success_intr(intr, pid, "dumpsys package", flags);
|
||||||
|
if (!ok) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r == -1) {
|
||||||
|
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';
|
||||||
|
|
||||||
|
return sc_adb_parse_installed_apk_version(buf);
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
|
|
||||||
#define SC_ADB_SILENT (SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR)
|
#define SC_ADB_SILENT (SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR)
|
||||||
|
|
||||||
|
#define SC_ANDROID_PACKAGE "com.genymobile.scrcpy"
|
||||||
|
|
||||||
const char *
|
const char *
|
||||||
sc_adb_get_executable(void);
|
sc_adb_get_executable(void);
|
||||||
|
|
||||||
@@ -114,4 +116,18 @@ sc_adb_getprop(struct sc_intr *intr, const char *serial, const char *prop,
|
|||||||
char *
|
char *
|
||||||
sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags);
|
sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the path of the installed APK for com.genymobile.scrcpy (if any)
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
sc_adb_get_installed_apk_path(struct sc_intr *intr, const char *serial,
|
||||||
|
unsigned flags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the version of the installed APK for com.genymobile.scrcpy (if any)
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
sc_adb_get_installed_apk_version(struct sc_intr *intr, const char *serial,
|
||||||
|
unsigned flags);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -225,3 +225,62 @@ sc_adb_parse_device_ip(char *str) {
|
|||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
sc_adb_parse_installed_apk_path(char *str) {
|
||||||
|
// str is expected to look like:
|
||||||
|
// "package:/data/app/.../base.apk=com.genymobile.scrcpy"
|
||||||
|
// ^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
// We want to extract the path (which may contain '=', even in practice)
|
||||||
|
|
||||||
|
if (strncmp(str, "package:", 8)) {
|
||||||
|
// Does not start with "package:"
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *s = str + 8;
|
||||||
|
size_t len = strcspn(s, " \r\n");
|
||||||
|
s[len] = '\0';
|
||||||
|
|
||||||
|
char *p = strrchr(s, '=');
|
||||||
|
if (!p) {
|
||||||
|
// No '=' found
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Truncate at the last '='
|
||||||
|
*p = '\0';
|
||||||
|
|
||||||
|
return strdup(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
sc_adb_parse_installed_apk_version(const char *str) {
|
||||||
|
// str is the (beginning of the) output of `dumpsys package`
|
||||||
|
// We want to extract the version string from a line starting with 4 spaces
|
||||||
|
// then `versionName=` then the version string.
|
||||||
|
|
||||||
|
#define VERSION_NAME_PREFIX "\n versionName="
|
||||||
|
char *s = strstr(str, VERSION_NAME_PREFIX);
|
||||||
|
if (!s) {
|
||||||
|
// Not found
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
s+= sizeof(VERSION_NAME_PREFIX) - 1;
|
||||||
|
|
||||||
|
size_t len = strspn(s, "0123456789.");
|
||||||
|
if (!len) {
|
||||||
|
LOGW("Unexpected version name with no value");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *version = malloc(len + 1);
|
||||||
|
if (!version) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(version, s, len);
|
||||||
|
version[len] = '\0';
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,4 +27,24 @@ sc_adb_parse_devices(char *str, struct sc_vec_adb_devices *out_vec);
|
|||||||
char *
|
char *
|
||||||
sc_adb_parse_device_ip(char *str);
|
sc_adb_parse_device_ip(char *str);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the package path from the output of
|
||||||
|
* `adb shell pm list packages -f <package>`
|
||||||
|
*
|
||||||
|
* The parameter must be a NUL-terminated string.
|
||||||
|
*
|
||||||
|
* Warning: this function modifies the buffer for optimization purposes.
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
sc_adb_parse_installed_apk_path(char *str);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the package version from the output of
|
||||||
|
* `adb shell dumpsys package <package>`
|
||||||
|
*
|
||||||
|
* The parameter must be a NUL-terminated string.
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
sc_adb_parse_installed_apk_version(const char *str);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,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,
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -160,6 +160,8 @@ execute_server(struct sc_server *server,
|
|||||||
const char *serial = server->serial;
|
const char *serial = server->serial;
|
||||||
assert(serial);
|
assert(serial);
|
||||||
|
|
||||||
|
fprintf(stderr, "=== [%s]\n", sc_adb_get_installed_apk_version(&server->intr, serial, 0));
|
||||||
|
|
||||||
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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -241,6 +241,54 @@ static void test_get_ip_truncated(void) {
|
|||||||
assert(!ip);
|
assert(!ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_apk_path(void) {
|
||||||
|
char str[] = "package:/data/app/~~71mguyc6p-kNjQdNaNkToA==/com.genymobile."
|
||||||
|
"scrcpy-l6fiqqUSU7Ok7QLg-rIyJA==/base.apk=com.genymobile."
|
||||||
|
"scrcpy\n";
|
||||||
|
|
||||||
|
const char *expected = "/data/app/~~71mguyc6p-kNjQdNaNkToA==/com.genymobile"
|
||||||
|
".scrcpy-l6fiqqUSU7Ok7QLg-rIyJA==/base.apk";
|
||||||
|
char *path = sc_adb_parse_installed_apk_path(str);
|
||||||
|
assert(!strcmp(path, expected));
|
||||||
|
free(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_apk_path_invalid(void) {
|
||||||
|
// Does not start with "package:"
|
||||||
|
char str[] = "garbage:/data/app/~~71mguyc6p-kNjQdNaNkToA==/com.genymobile."
|
||||||
|
"scrcpy-l6fiqqUSU7Ok7QLg-rIyJA==/base.apk=com.genymobile."
|
||||||
|
"scrcpy\n";
|
||||||
|
|
||||||
|
char *path = sc_adb_parse_installed_apk_path(str);
|
||||||
|
assert(!path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_apk_version(void) {
|
||||||
|
char str[] =
|
||||||
|
"Key Set Manager:\n"
|
||||||
|
" [com.genymobile.scrcpy]\n"
|
||||||
|
" Signing KeySets: 128\n"
|
||||||
|
"\n"
|
||||||
|
"Packages:\n"
|
||||||
|
" Package [com.genymobile.scrcpy] (89abcdef):\n"
|
||||||
|
" userId=12345\n"
|
||||||
|
" pkg=Package{012345 com.genymobile.scrcpy}\n"
|
||||||
|
" codePath=/data/app/~~abcdef==/com.genymobile.scrcpy-012345==\n"
|
||||||
|
" resourcePath=/data/app/~~abcdef==/com.genymobile.scrcpy-013245==\n"
|
||||||
|
" primaryCpuAbi=null\n"
|
||||||
|
" secondaryCpuAbi=null\n"
|
||||||
|
" versionCode=12400 minSdk=21 targetSdk=31\n"
|
||||||
|
" versionName=1.24\n"
|
||||||
|
" splits=[base]\n"
|
||||||
|
" apkSigningVersion=2\n"
|
||||||
|
" applicationInfo=ApplicationInfo{012345 com.genymobile.scrcpy}\n";
|
||||||
|
|
||||||
|
const char *expected = "1.24";
|
||||||
|
char *version = sc_adb_parse_installed_apk_version(str);
|
||||||
|
assert(!strcmp(version, expected));
|
||||||
|
free(version);
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
(void) argc;
|
(void) argc;
|
||||||
(void) argv;
|
(void) argv;
|
||||||
@@ -263,5 +311,9 @@ int main(int argc, char *argv[]) {
|
|||||||
test_get_ip_no_wlan_without_eol();
|
test_get_ip_no_wlan_without_eol();
|
||||||
test_get_ip_truncated();
|
test_get_ip_truncated();
|
||||||
|
|
||||||
|
test_apk_path();
|
||||||
|
test_apk_path_invalid();
|
||||||
|
test_apk_version();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
81
app/tests/test_buffer_util.c
Normal file
81
app/tests/test_buffer_util.c
Normal 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;
|
||||||
|
}
|
||||||
@@ -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)));
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
testImplementation 'junit:junit:4.13.1'
|
testImplementation 'junit:junit:4.13.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ package com.genymobile.scrcpy.wrappers;
|
|||||||
import com.genymobile.scrcpy.DisplayInfo;
|
import com.genymobile.scrcpy.DisplayInfo;
|
||||||
import com.genymobile.scrcpy.Size;
|
import com.genymobile.scrcpy.Size;
|
||||||
|
|
||||||
public final class DisplayManager {
|
import android.os.IInterface;
|
||||||
private final Object manager; // instance of hidden class android.hardware.display.DisplayManagerGlobal
|
|
||||||
|
|
||||||
public DisplayManager(Object manager) {
|
public final class DisplayManager {
|
||||||
|
private final IInterface manager;
|
||||||
|
|
||||||
|
public DisplayManager(IInterface manager) {
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user