Compare commits
1 Commits
acksync
...
feature_te
Author | SHA1 | Date | |
---|---|---|---|
95769eb87b |
37
README.md
37
README.md
@ -412,47 +412,12 @@ autoadb scrcpy -s '{}'
|
|||||||
|
|
||||||
[AutoAdb]: https://github.com/rom1v/autoadb
|
[AutoAdb]: https://github.com/rom1v/autoadb
|
||||||
|
|
||||||
#### Tunnels
|
#### SSH tunnel
|
||||||
|
|
||||||
To connect to a remote device, it is possible to connect a local `adb` client to
|
To connect to a remote device, it is possible to connect a local `adb` client to
|
||||||
a remote `adb` server (provided they use the same version of the _adb_
|
a remote `adb` server (provided they use the same version of the _adb_
|
||||||
protocol).
|
protocol).
|
||||||
|
|
||||||
##### Remote ADB server
|
|
||||||
|
|
||||||
To connect to a remote ADB server, make the server listen on all interfaces:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
adb kill-server
|
|
||||||
adb -a nodaemon server start
|
|
||||||
# keep this open
|
|
||||||
```
|
|
||||||
|
|
||||||
**Warning: all communications between clients and ADB server are unencrypted.**
|
|
||||||
|
|
||||||
Suppose that this server is accessible at 192.168.1.2. Then, from another
|
|
||||||
terminal, run scrcpy:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export ADB_SERVER_SOCKET=tcp:192.168.1.2:5037
|
|
||||||
scrcpy --tunnel-host=192.168.1.2
|
|
||||||
```
|
|
||||||
|
|
||||||
By default, scrcpy uses the local port used for `adb forward` tunnel
|
|
||||||
establishment (typically `27183`, see `--port`). It is also possible to force a
|
|
||||||
different tunnel port (it may be useful in more complex situations, when more
|
|
||||||
redirections are involved):
|
|
||||||
|
|
||||||
```
|
|
||||||
scrcpy --tunnel-port=1234
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
##### SSH tunnel
|
|
||||||
|
|
||||||
To communicate with a remote ADB server securely, it is preferable to use a SSH
|
|
||||||
tunnel.
|
|
||||||
|
|
||||||
First, make sure the ADB server is running on the remote computer:
|
First, make sure the ADB server is running on the remote computer:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
@ -25,7 +25,6 @@ src = [
|
|||||||
'src/server.c',
|
'src/server.c',
|
||||||
'src/stream.c',
|
'src/stream.c',
|
||||||
'src/video_buffer.c',
|
'src/video_buffer.c',
|
||||||
'src/util/acksync.c',
|
|
||||||
'src/util/file.c',
|
'src/util/file.c',
|
||||||
'src/util/intr.c',
|
'src/util/intr.c',
|
||||||
'src/util/log.c',
|
'src/util/log.c',
|
||||||
@ -40,25 +39,23 @@ src = [
|
|||||||
'src/util/tick.c',
|
'src/util/tick.c',
|
||||||
]
|
]
|
||||||
|
|
||||||
conf = configuration_data()
|
|
||||||
|
|
||||||
if host_machine.system() == 'windows'
|
if host_machine.system() == 'windows'
|
||||||
src += [
|
src += [
|
||||||
'src/sys/win/file.c',
|
'src/sys/win/file.c',
|
||||||
'src/sys/win/process.c',
|
'src/sys/win/process.c',
|
||||||
]
|
]
|
||||||
conf.set('_WIN32_WINNT', '0x0600')
|
add_project_arguments('-D_WIN32_WINNT=0x0600', language: 'c')
|
||||||
conf.set('WINVER', '0x0600')
|
add_project_arguments('-DWINVER=0x0600', language: 'c')
|
||||||
else
|
else
|
||||||
src += [
|
src += [
|
||||||
'src/sys/unix/file.c',
|
'src/sys/unix/file.c',
|
||||||
'src/sys/unix/process.c',
|
'src/sys/unix/process.c',
|
||||||
]
|
]
|
||||||
conf.set('_POSIX_C_SOURCE', '200809L')
|
add_project_arguments('-D_POSIX_C_SOURCE=200809L', language: 'c')
|
||||||
conf.set('_XOPEN_SOURCE', '700')
|
add_project_arguments('-D_XOPEN_SOURCE=700', language: 'c')
|
||||||
conf.set('_GNU_SOURCE', true)
|
add_project_arguments('-D_GNU_SOURCE', language: 'c')
|
||||||
if host_machine.system() == 'darwin'
|
if host_machine.system() == 'darwin'
|
||||||
conf.set('_DARWIN_C_SOURCE', true)
|
add_project_arguments('-D_DARWIN_C_SOURCE', language: 'c')
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
@ -139,6 +136,8 @@ if host_machine.system() == 'windows'
|
|||||||
dependencies += cc.find_library('ws2_32')
|
dependencies += cc.find_library('ws2_32')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
conf = configuration_data()
|
||||||
|
|
||||||
foreach f : check_functions
|
foreach f : check_functions
|
||||||
if cc.has_function(f)
|
if cc.has_function(f)
|
||||||
define = 'HAVE_' + f.underscorify().to_upper()
|
define = 'HAVE_' + f.underscorify().to_upper()
|
||||||
@ -208,7 +207,6 @@ if get_option('buildtype') == 'debug'
|
|||||||
'tests/test_cli.c',
|
'tests/test_cli.c',
|
||||||
'src/cli.c',
|
'src/cli.c',
|
||||||
'src/options.c',
|
'src/options.c',
|
||||||
'src/util/net.c',
|
|
||||||
'src/util/str.c',
|
'src/util/str.c',
|
||||||
'src/util/strbuf.c',
|
'src/util/strbuf.c',
|
||||||
'src/util/term.c',
|
'src/util/term.c',
|
||||||
|
12
app/scrcpy.1
12
app/scrcpy.1
@ -203,18 +203,6 @@ Enable "show touches" on start, restore the initial value on exit.
|
|||||||
|
|
||||||
It only shows physical touches (not clicks from scrcpy).
|
It only shows physical touches (not clicks from scrcpy).
|
||||||
|
|
||||||
.TP
|
|
||||||
.BI "\-\-tunnel\-host " ip
|
|
||||||
Set the IP address of the adb tunnel to reach the scrcpy server. This option automatically enables --force-adb-forward.
|
|
||||||
|
|
||||||
Default is localhost.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.BI "\-\-tunnel\-port " port
|
|
||||||
Set the TCP port of the adb tunnel to reach the scrcpy server. This option automatically enables --force-adb-forward.
|
|
||||||
|
|
||||||
Default is 0 (not forced): the local port used for establishing the tunnel will be used.
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-v4l2-sink " /dev/videoN
|
.BI "\-\-v4l2-sink " /dev/videoN
|
||||||
Output to v4l2loopback device.
|
Output to v4l2loopback device.
|
||||||
|
@ -35,7 +35,7 @@ sc_hid_event_init(struct sc_hid_event *hid_event, uint16_t accessory_id,
|
|||||||
hid_event->accessory_id = accessory_id;
|
hid_event->accessory_id = accessory_id;
|
||||||
hid_event->buffer = buffer;
|
hid_event->buffer = buffer;
|
||||||
hid_event->size = buffer_size;
|
hid_event->size = buffer_size;
|
||||||
hid_event->ack_to_wait = SC_SEQUENCE_INVALID;
|
hid_event->delay = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@ -118,10 +118,7 @@ sc_aoa_open_usb_handle(libusb_device *device, libusb_device_handle **handle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_aoa_init(struct sc_aoa *aoa, const char *serial,
|
sc_aoa_init(struct sc_aoa *aoa, const char *serial) {
|
||||||
struct sc_acksync *acksync) {
|
|
||||||
assert(acksync);
|
|
||||||
|
|
||||||
cbuf_init(&aoa->queue);
|
cbuf_init(&aoa->queue);
|
||||||
|
|
||||||
if (!sc_mutex_init(&aoa->mutex)) {
|
if (!sc_mutex_init(&aoa->mutex)) {
|
||||||
@ -158,7 +155,6 @@ sc_aoa_init(struct sc_aoa *aoa, const char *serial,
|
|||||||
}
|
}
|
||||||
|
|
||||||
aoa->stopped = false;
|
aoa->stopped = false;
|
||||||
aoa->acksync = acksync;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -336,28 +332,23 @@ run_aoa_thread(void *data) {
|
|||||||
assert(non_empty);
|
assert(non_empty);
|
||||||
(void) non_empty;
|
(void) non_empty;
|
||||||
|
|
||||||
uint64_t ack_to_wait = event.ack_to_wait;
|
assert(event.delay >= 0);
|
||||||
|
if (event.delay) {
|
||||||
|
// Wait during the specified delay before injecting the HID event
|
||||||
|
sc_tick deadline = sc_tick_now() + event.delay;
|
||||||
|
bool timed_out = false;
|
||||||
|
while (!aoa->stopped && !timed_out) {
|
||||||
|
timed_out = !sc_cond_timedwait(&aoa->event_cond, &aoa->mutex,
|
||||||
|
deadline);
|
||||||
|
}
|
||||||
|
if (aoa->stopped) {
|
||||||
sc_mutex_unlock(&aoa->mutex);
|
sc_mutex_unlock(&aoa->mutex);
|
||||||
|
|
||||||
if (ack_to_wait != SC_SEQUENCE_INVALID) {
|
|
||||||
LOGD("Waiting ack from server sequence=%" PRIu64_, ack_to_wait);
|
|
||||||
// Do not block the loop indefinitely if the ack never comes (it should
|
|
||||||
// never happen)
|
|
||||||
sc_tick deadline = sc_tick_now() + SC_TICK_FROM_MS(500);
|
|
||||||
enum sc_acksync_wait_result result =
|
|
||||||
sc_acksync_wait(aoa->acksync, ack_to_wait, deadline);
|
|
||||||
|
|
||||||
if (result == SC_ACKSYNC_WAIT_TIMEOUT) {
|
|
||||||
LOGW("Ack not received after 500ms, discarding HID event");
|
|
||||||
sc_hid_event_destroy(&event);
|
|
||||||
continue;
|
|
||||||
} else if (result == SC_ACKSYNC_WAIT_INTR) {
|
|
||||||
// stopped
|
|
||||||
sc_hid_event_destroy(&event);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sc_mutex_unlock(&aoa->mutex);
|
||||||
|
|
||||||
bool ok = sc_aoa_send_hid_event(aoa, &event);
|
bool ok = sc_aoa_send_hid_event(aoa, &event);
|
||||||
sc_hid_event_destroy(&event);
|
sc_hid_event_destroy(&event);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
@ -386,8 +377,6 @@ sc_aoa_stop(struct sc_aoa *aoa) {
|
|||||||
aoa->stopped = true;
|
aoa->stopped = true;
|
||||||
sc_cond_signal(&aoa->event_cond);
|
sc_cond_signal(&aoa->event_cond);
|
||||||
sc_mutex_unlock(&aoa->mutex);
|
sc_mutex_unlock(&aoa->mutex);
|
||||||
|
|
||||||
sc_acksync_interrupt(aoa->acksync);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
#include <libusb-1.0/libusb.h>
|
#include <libusb-1.0/libusb.h>
|
||||||
|
|
||||||
#include "util/acksync.h"
|
|
||||||
#include "util/cbuf.h"
|
#include "util/cbuf.h"
|
||||||
#include "util/thread.h"
|
#include "util/thread.h"
|
||||||
#include "util/tick.h"
|
#include "util/tick.h"
|
||||||
@ -15,7 +14,7 @@ struct sc_hid_event {
|
|||||||
uint16_t accessory_id;
|
uint16_t accessory_id;
|
||||||
unsigned char *buffer;
|
unsigned char *buffer;
|
||||||
uint16_t size;
|
uint16_t size;
|
||||||
uint64_t ack_to_wait;
|
sc_tick delay;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Takes ownership of buffer
|
// Takes ownership of buffer
|
||||||
@ -37,12 +36,10 @@ struct sc_aoa {
|
|||||||
sc_cond event_cond;
|
sc_cond event_cond;
|
||||||
bool stopped;
|
bool stopped;
|
||||||
struct sc_hid_event_queue queue;
|
struct sc_hid_event_queue queue;
|
||||||
|
|
||||||
struct sc_acksync *acksync;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_aoa_init(struct sc_aoa *aoa, const char *serial, struct sc_acksync *acksync);
|
sc_aoa_init(struct sc_aoa *aoa, const char *serial);
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_aoa_destroy(struct sc_aoa *aoa);
|
sc_aoa_destroy(struct sc_aoa *aoa);
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
|
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/net.h"
|
|
||||||
#include "util/str.h"
|
#include "util/str.h"
|
||||||
#include "util/strbuf.h"
|
#include "util/strbuf.h"
|
||||||
#include "util/term.h"
|
#include "util/term.h"
|
||||||
@ -47,8 +46,6 @@
|
|||||||
#define OPT_V4L2_SINK 1027
|
#define OPT_V4L2_SINK 1027
|
||||||
#define OPT_DISPLAY_BUFFER 1028
|
#define OPT_DISPLAY_BUFFER 1028
|
||||||
#define OPT_V4L2_BUFFER 1029
|
#define OPT_V4L2_BUFFER 1029
|
||||||
#define OPT_TUNNEL_HOST 1030
|
|
||||||
#define OPT_TUNNEL_PORT 1031
|
|
||||||
|
|
||||||
struct sc_option {
|
struct sc_option {
|
||||||
char shortopt;
|
char shortopt;
|
||||||
@ -333,25 +330,6 @@ static const struct sc_option options[] = {
|
|||||||
"on exit.\n"
|
"on exit.\n"
|
||||||
"It only shows physical touches (not clicks from scrcpy).",
|
"It only shows physical touches (not clicks from scrcpy).",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
.longopt_id = OPT_TUNNEL_HOST,
|
|
||||||
.longopt = "tunnel-host",
|
|
||||||
.argdesc = "ip",
|
|
||||||
.text = "Set the IP address of the adb tunnel to reach the scrcpy "
|
|
||||||
"server. This option automatically enables "
|
|
||||||
"--force-adb-forward.\n"
|
|
||||||
"Default is localhost.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.longopt_id = OPT_TUNNEL_PORT,
|
|
||||||
.longopt = "tunnel-port",
|
|
||||||
.argdesc = "port",
|
|
||||||
.text = "Set the TCP port of the adb tunnel to reach the scrcpy "
|
|
||||||
"server. This option automatically enables "
|
|
||||||
"--force-adb-forward.\n"
|
|
||||||
"Default is 0 (not forced): the local port used for "
|
|
||||||
"establishing the tunnel will be used.",
|
|
||||||
},
|
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_V4L2_SINK,
|
.longopt_id = OPT_V4L2_SINK,
|
||||||
@ -1149,21 +1127,6 @@ parse_record_format(const char *optarg, enum sc_record_format *format) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
|
||||||
parse_ip(const char *optarg, uint32_t *ipv4) {
|
|
||||||
return net_parse_ipv4(optarg, ipv4);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
parse_port(const char *optarg, uint16_t *port) {
|
|
||||||
long value;
|
|
||||||
if (!parse_integer_arg(optarg, &value, false, 0, 0xFFFF, "port")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
*port = (uint16_t) value;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static enum sc_record_format
|
static enum sc_record_format
|
||||||
guess_record_format(const char *filename) {
|
guess_record_format(const char *filename) {
|
||||||
size_t len = strlen(filename);
|
size_t len = strlen(filename);
|
||||||
@ -1236,16 +1199,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case OPT_TUNNEL_HOST:
|
|
||||||
if (!parse_ip(optarg, &opts->tunnel_host)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case OPT_TUNNEL_PORT:
|
|
||||||
if (!parse_port(optarg, &opts->tunnel_port)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'n':
|
case 'n':
|
||||||
opts->control = false;
|
opts->control = false;
|
||||||
break;
|
break;
|
||||||
@ -1405,12 +1358,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if ((opts->tunnel_host || opts->tunnel_port) && !opts->force_adb_forward) {
|
|
||||||
LOGI("Tunnel host/port is set, "
|
|
||||||
"--force-adb-forward automatically enabled.");
|
|
||||||
opts->force_adb_forward = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int index = optind;
|
int index = optind;
|
||||||
if (index < argc) {
|
if (index < argc) {
|
||||||
LOGE("Unexpected additional argument: %s", argv[index]);
|
LOGE("Unexpected additional argument: %s", argv[index]);
|
||||||
|
@ -1,17 +1,9 @@
|
|||||||
#ifndef COMPAT_H
|
#ifndef COMPAT_H
|
||||||
#define COMPAT_H
|
#define COMPAT_H
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include <libavformat/version.h>
|
#include <libavformat/version.h>
|
||||||
#include <SDL2/SDL_version.h>
|
#include <SDL2/SDL_version.h>
|
||||||
|
|
||||||
#ifndef __WIN32
|
|
||||||
# define PRIu64_ PRIu64
|
|
||||||
#else
|
|
||||||
# define PRIu64_ "I64u" // Windows...
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// In ffmpeg/doc/APIchanges:
|
// In ffmpeg/doc/APIchanges:
|
||||||
// 2018-02-06 - 0694d87024 - lavf 58.9.100 - avformat.h
|
// 2018-02-06 - 0694d87024 - lavf 58.9.100 - avformat.h
|
||||||
// Deprecate use of av_register_input_format(), av_register_output_format(),
|
// Deprecate use of av_register_input_format(), av_register_output_format(),
|
||||||
|
@ -118,12 +118,11 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
|||||||
buf[1] = msg->inject_keycode.action;
|
buf[1] = msg->inject_keycode.action;
|
||||||
return 2;
|
return 2;
|
||||||
case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
|
case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
|
||||||
buffer_write64be(&buf[1], msg->set_clipboard.sequence);
|
buf[1] = !!msg->set_clipboard.paste;
|
||||||
buf[9] = !!msg->set_clipboard.paste;
|
|
||||||
size_t len = write_string(msg->set_clipboard.text,
|
size_t len = write_string(msg->set_clipboard.text,
|
||||||
CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH,
|
CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH,
|
||||||
&buf[10]);
|
&buf[2]);
|
||||||
return 10 + len;
|
return 2 + len;
|
||||||
}
|
}
|
||||||
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
||||||
buf[1] = msg->set_screen_power_mode.mode;
|
buf[1] = msg->set_screen_power_mode.mode;
|
||||||
@ -171,6 +170,11 @@ control_msg_log(const struct control_msg *msg) {
|
|||||||
(long) msg->inject_touch_event.buttons);
|
(long) msg->inject_touch_event.buttons);
|
||||||
} else {
|
} else {
|
||||||
// numeric pointer id
|
// numeric pointer id
|
||||||
|
#ifndef __WIN32
|
||||||
|
# define PRIu64_ PRIu64
|
||||||
|
#else
|
||||||
|
# define PRIu64_ "I64u" // Windows...
|
||||||
|
#endif
|
||||||
LOG_CMSG("touch [id=%" PRIu64_ "] %-4s position=%" PRIi32 ",%"
|
LOG_CMSG("touch [id=%" PRIu64_ "] %-4s position=%" PRIi32 ",%"
|
||||||
PRIi32 " pressure=%g buttons=%06lx",
|
PRIi32 " pressure=%g buttons=%06lx",
|
||||||
id,
|
id,
|
||||||
@ -195,8 +199,7 @@ control_msg_log(const struct control_msg *msg) {
|
|||||||
KEYEVENT_ACTION_LABEL(msg->inject_keycode.action));
|
KEYEVENT_ACTION_LABEL(msg->inject_keycode.action));
|
||||||
break;
|
break;
|
||||||
case CONTROL_MSG_TYPE_SET_CLIPBOARD:
|
case CONTROL_MSG_TYPE_SET_CLIPBOARD:
|
||||||
LOG_CMSG("clipboard %" PRIu64_ " %s \"%s\"",
|
LOG_CMSG("clipboard %s \"%s\"",
|
||||||
msg->set_clipboard.sequence,
|
|
||||||
msg->set_clipboard.paste ? "paste" : "copy",
|
msg->set_clipboard.paste ? "paste" : "copy",
|
||||||
msg->set_clipboard.text);
|
msg->set_clipboard.text);
|
||||||
break;
|
break;
|
||||||
|
@ -70,7 +70,6 @@ struct control_msg {
|
|||||||
// screen may only be turned on on ACTION_DOWN
|
// screen may only be turned on on ACTION_DOWN
|
||||||
} back_or_screen_on;
|
} back_or_screen_on;
|
||||||
struct {
|
struct {
|
||||||
uint64_t sequence;
|
|
||||||
char *text; // owned, to be freed by free()
|
char *text; // owned, to be freed by free()
|
||||||
bool paste;
|
bool paste;
|
||||||
} set_clipboard;
|
} set_clipboard;
|
||||||
|
@ -5,11 +5,10 @@
|
|||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
bool
|
bool
|
||||||
controller_init(struct controller *controller, sc_socket control_socket,
|
controller_init(struct controller *controller, sc_socket control_socket) {
|
||||||
struct sc_acksync *acksync) {
|
|
||||||
cbuf_init(&controller->queue);
|
cbuf_init(&controller->queue);
|
||||||
|
|
||||||
bool ok = receiver_init(&controller->receiver, control_socket, acksync);
|
bool ok = receiver_init(&controller->receiver, control_socket);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
#include "control_msg.h"
|
#include "control_msg.h"
|
||||||
#include "receiver.h"
|
#include "receiver.h"
|
||||||
#include "util/acksync.h"
|
|
||||||
#include "util/cbuf.h"
|
#include "util/cbuf.h"
|
||||||
#include "util/net.h"
|
#include "util/net.h"
|
||||||
#include "util/thread.h"
|
#include "util/thread.h"
|
||||||
@ -25,8 +24,7 @@ struct controller {
|
|||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
controller_init(struct controller *controller, sc_socket control_socket,
|
controller_init(struct controller *controller, sc_socket control_socket);
|
||||||
struct sc_acksync *acksync);
|
|
||||||
|
|
||||||
void
|
void
|
||||||
controller_destroy(struct controller *controller);
|
controller_destroy(struct controller *controller);
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
#include "device_msg.h"
|
#include "device_msg.h"
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
@ -35,11 +34,6 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
|
|||||||
msg->clipboard.text = text;
|
msg->clipboard.text = text;
|
||||||
return 5 + clipboard_len;
|
return 5 + clipboard_len;
|
||||||
}
|
}
|
||||||
case DEVICE_MSG_TYPE_ACK_CLIPBOARD: {
|
|
||||||
uint64_t sequence = buffer_read64be(&buf[1]);
|
|
||||||
msg->ack_clipboard.sequence = sequence;
|
|
||||||
return 9;
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
LOGW("Unknown device message type: %d", (int) msg->type);
|
LOGW("Unknown device message type: %d", (int) msg->type);
|
||||||
return -1; // error, we cannot recover
|
return -1; // error, we cannot recover
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
|
|
||||||
enum device_msg_type {
|
enum device_msg_type {
|
||||||
DEVICE_MSG_TYPE_CLIPBOARD,
|
DEVICE_MSG_TYPE_CLIPBOARD,
|
||||||
DEVICE_MSG_TYPE_ACK_CLIPBOARD,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct device_msg {
|
struct device_msg {
|
||||||
@ -22,9 +21,6 @@ struct device_msg {
|
|||||||
struct {
|
struct {
|
||||||
char *text; // owned, to be freed by free()
|
char *text; // owned, to be freed by free()
|
||||||
} clipboard;
|
} clipboard;
|
||||||
struct {
|
|
||||||
uint64_t sequence;
|
|
||||||
} ack_clipboard;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -278,8 +278,7 @@ push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t sdl_mod) {
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
sc_key_processor_process_key(struct sc_key_processor *kp,
|
sc_key_processor_process_key(struct sc_key_processor *kp,
|
||||||
const SDL_KeyboardEvent *event,
|
const SDL_KeyboardEvent *event) {
|
||||||
uint64_t ack_to_wait) {
|
|
||||||
if (event->repeat) {
|
if (event->repeat) {
|
||||||
// In USB HID protocol, key repeat is handled by the host (Android), so
|
// In USB HID protocol, key repeat is handled by the host (Android), so
|
||||||
// just ignore key repeat here.
|
// just ignore key repeat here.
|
||||||
@ -299,12 +298,16 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ack_to_wait) {
|
SDL_Keycode keycode = event->keysym.sym;
|
||||||
|
bool down = event->type == SDL_KEYDOWN;
|
||||||
|
bool ctrl = event->keysym.mod & KMOD_CTRL;
|
||||||
|
bool shift = event->keysym.mod & KMOD_SHIFT;
|
||||||
|
if (ctrl && !shift && keycode == SDLK_v && down) {
|
||||||
// Ctrl+v is pressed, so clipboard synchronization has been
|
// Ctrl+v is pressed, so clipboard synchronization has been
|
||||||
// requested. Wait until clipboard synchronization is acknowledged
|
// requested. Wait a bit so that the clipboard is set before
|
||||||
// by the server, otherwise it could paste the old clipboard
|
// injecting Ctrl+v via HID, otherwise it would paste the old
|
||||||
// content.
|
// clipboard content.
|
||||||
hid_event.ack_to_wait = ack_to_wait;
|
hid_event.delay = SC_TICK_FROM_MS(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) {
|
if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) {
|
||||||
@ -345,10 +348,6 @@ sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa) {
|
|||||||
.process_text = sc_key_processor_process_text,
|
.process_text = sc_key_processor_process_text,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Clipboard synchronization is requested over the control socket, while HID
|
|
||||||
// events are sent over AOA, so it must wait for clipboard synchronization
|
|
||||||
// to be acknowledged by the device before injecting Ctrl+v.
|
|
||||||
kb->key_processor.async_paste = true;
|
|
||||||
kb->key_processor.ops = &ops;
|
kb->key_processor.ops = &ops;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -82,8 +82,6 @@ input_manager_init(struct input_manager *im, struct controller *controller,
|
|||||||
im->last_keycode = SDLK_UNKNOWN;
|
im->last_keycode = SDLK_UNKNOWN;
|
||||||
im->last_mod = 0;
|
im->last_mod = 0;
|
||||||
im->key_repeat = 0;
|
im->key_repeat = 0;
|
||||||
|
|
||||||
im->next_sequence = 1; // 0 is reserved for SC_SEQUENCE_INVALID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -210,35 +208,35 @@ collapse_panels(struct controller *controller) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static void
|
||||||
set_device_clipboard(struct controller *controller, bool paste,
|
set_device_clipboard(struct controller *controller, bool paste) {
|
||||||
uint64_t sequence) {
|
|
||||||
char *text = SDL_GetClipboardText();
|
char *text = SDL_GetClipboardText();
|
||||||
if (!text) {
|
if (!text) {
|
||||||
LOGW("Could not get clipboard text: %s", SDL_GetError());
|
LOGW("Could not get clipboard text: %s", SDL_GetError());
|
||||||
return false;
|
return;
|
||||||
|
}
|
||||||
|
if (!*text) {
|
||||||
|
// empty text
|
||||||
|
SDL_free(text);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *text_dup = strdup(text);
|
char *text_dup = strdup(text);
|
||||||
SDL_free(text);
|
SDL_free(text);
|
||||||
if (!text_dup) {
|
if (!text_dup) {
|
||||||
LOGW("Could not strdup input text");
|
LOGW("Could not strdup input text");
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD;
|
msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD;
|
||||||
msg.set_clipboard.sequence = sequence;
|
|
||||||
msg.set_clipboard.text = text_dup;
|
msg.set_clipboard.text = text_dup;
|
||||||
msg.set_clipboard.paste = paste;
|
msg.set_clipboard.paste = paste;
|
||||||
|
|
||||||
if (!controller_push_msg(controller, &msg)) {
|
if (!controller_push_msg(controller, &msg)) {
|
||||||
free(text_dup);
|
free(text_dup);
|
||||||
LOGW("Could not request 'set device clipboard'");
|
LOGW("Could not request 'set device clipboard'");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -464,10 +462,8 @@ input_manager_process_key(struct input_manager *im,
|
|||||||
// inject the text as input events
|
// inject the text as input events
|
||||||
clipboard_paste(controller);
|
clipboard_paste(controller);
|
||||||
} else {
|
} else {
|
||||||
// store the text in the device clipboard and paste,
|
// store the text in the device clipboard and paste
|
||||||
// without requesting an acknowledgment
|
set_device_clipboard(controller, true);
|
||||||
set_device_clipboard(controller, true,
|
|
||||||
SC_SEQUENCE_INVALID);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -516,36 +512,18 @@ input_manager_process_key(struct input_manager *im,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t ack_to_wait = SC_SEQUENCE_INVALID;
|
if (ctrl && !shift && keycode == SDLK_v && down && !repeat) {
|
||||||
bool is_ctrl_v = ctrl && !shift && keycode == SDLK_v && down && !repeat;
|
|
||||||
if (is_ctrl_v) {
|
|
||||||
if (im->legacy_paste) {
|
if (im->legacy_paste) {
|
||||||
// inject the text as input events
|
// inject the text as input events
|
||||||
clipboard_paste(controller);
|
clipboard_paste(controller);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request an acknowledgement only if necessary
|
|
||||||
uint64_t sequence = im->kp->async_paste ? im->next_sequence
|
|
||||||
: SC_SEQUENCE_INVALID;
|
|
||||||
|
|
||||||
// Synchronize the computer clipboard to the device clipboard before
|
// Synchronize the computer clipboard to the device clipboard before
|
||||||
// sending Ctrl+v, to allow seamless copy-paste.
|
// sending Ctrl+v, to allow seamless copy-paste.
|
||||||
bool ok = set_device_clipboard(controller, false, sequence);
|
set_device_clipboard(controller, false);
|
||||||
if (!ok) {
|
|
||||||
LOGW("Clipboard could not be synchronized, Ctrl+v not injected");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (im->kp->async_paste) {
|
im->kp->ops->process_key(im->kp, event);
|
||||||
// The key processor must wait for this ack before injecting Ctrl+v
|
|
||||||
ack_to_wait = sequence;
|
|
||||||
// Increment only when the request succeeded
|
|
||||||
++im->next_sequence;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
im->kp->ops->process_key(im->kp, event, ack_to_wait);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -38,8 +38,6 @@ struct input_manager {
|
|||||||
unsigned key_repeat;
|
unsigned key_repeat;
|
||||||
SDL_Keycode last_keycode;
|
SDL_Keycode last_keycode;
|
||||||
uint16_t last_mod;
|
uint16_t last_mod;
|
||||||
|
|
||||||
uint64_t next_sequence; // used for request acknowledgements
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -188,13 +188,7 @@ convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
sc_key_processor_process_key(struct sc_key_processor *kp,
|
sc_key_processor_process_key(struct sc_key_processor *kp,
|
||||||
const SDL_KeyboardEvent *event,
|
const SDL_KeyboardEvent *event) {
|
||||||
uint64_t ack_to_wait) {
|
|
||||||
// The device clipboard synchronization and the key event messages are
|
|
||||||
// serialized, there is nothing special to do to ensure that the clipboard
|
|
||||||
// is set before injecting Ctrl+v.
|
|
||||||
(void) ack_to_wait;
|
|
||||||
|
|
||||||
struct sc_keyboard_inject *ki = DOWNCAST(kp);
|
struct sc_keyboard_inject *ki = DOWNCAST(kp);
|
||||||
|
|
||||||
if (event->repeat) {
|
if (event->repeat) {
|
||||||
@ -256,7 +250,5 @@ sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
|
|||||||
.process_text = sc_key_processor_process_text,
|
.process_text = sc_key_processor_process_text,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Key injection and clipboard synchronization are serialized
|
|
||||||
ki->key_processor.async_paste = false;
|
|
||||||
ki->key_processor.ops = &ops;
|
ki->key_processor.ops = &ops;
|
||||||
}
|
}
|
||||||
|
@ -19,8 +19,6 @@ const struct scrcpy_options scrcpy_options_default = {
|
|||||||
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST,
|
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST,
|
||||||
.last = DEFAULT_LOCAL_PORT_RANGE_LAST,
|
.last = DEFAULT_LOCAL_PORT_RANGE_LAST,
|
||||||
},
|
},
|
||||||
.tunnel_host = 0,
|
|
||||||
.tunnel_port = 0,
|
|
||||||
.shortcut_mods = {
|
.shortcut_mods = {
|
||||||
.data = {SC_MOD_LALT, SC_MOD_LSUPER},
|
.data = {SC_MOD_LALT, SC_MOD_LSUPER},
|
||||||
.count = 2,
|
.count = 2,
|
||||||
|
@ -77,8 +77,6 @@ struct scrcpy_options {
|
|||||||
enum sc_record_format record_format;
|
enum sc_record_format record_format;
|
||||||
enum sc_keyboard_input_mode keyboard_input_mode;
|
enum sc_keyboard_input_mode keyboard_input_mode;
|
||||||
struct sc_port_range port_range;
|
struct sc_port_range port_range;
|
||||||
uint32_t tunnel_host;
|
|
||||||
uint16_t tunnel_port;
|
|
||||||
struct sc_shortcut_mods shortcut_mods;
|
struct sc_shortcut_mods shortcut_mods;
|
||||||
uint16_t max_size;
|
uint16_t max_size;
|
||||||
uint32_t bit_rate;
|
uint32_t bit_rate;
|
||||||
|
@ -7,16 +7,12 @@
|
|||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
bool
|
bool
|
||||||
receiver_init(struct receiver *receiver, sc_socket control_socket,
|
receiver_init(struct receiver *receiver, sc_socket control_socket) {
|
||||||
struct sc_acksync *acksync) {
|
|
||||||
bool ok = sc_mutex_init(&receiver->mutex);
|
bool ok = sc_mutex_init(&receiver->mutex);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
receiver->control_socket = control_socket;
|
receiver->control_socket = control_socket;
|
||||||
receiver->acksync = acksync;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,7 +22,7 @@ receiver_destroy(struct receiver *receiver) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
process_msg(struct receiver *receiver, struct device_msg *msg) {
|
process_msg(struct device_msg *msg) {
|
||||||
switch (msg->type) {
|
switch (msg->type) {
|
||||||
case DEVICE_MSG_TYPE_CLIPBOARD: {
|
case DEVICE_MSG_TYPE_CLIPBOARD: {
|
||||||
char *current = SDL_GetClipboardText();
|
char *current = SDL_GetClipboardText();
|
||||||
@ -41,17 +37,11 @@ process_msg(struct receiver *receiver, struct device_msg *msg) {
|
|||||||
SDL_SetClipboardText(msg->clipboard.text);
|
SDL_SetClipboardText(msg->clipboard.text);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DEVICE_MSG_TYPE_ACK_CLIPBOARD:
|
|
||||||
assert(receiver->acksync);
|
|
||||||
LOGD("Ack device clipboard sequence=%" PRIu64_,
|
|
||||||
msg->ack_clipboard.sequence);
|
|
||||||
sc_acksync_ack(receiver->acksync, msg->ack_clipboard.sequence);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t
|
static ssize_t
|
||||||
process_msgs(struct receiver *receiver, const unsigned char *buf, size_t len) {
|
process_msgs(const unsigned char *buf, size_t len) {
|
||||||
size_t head = 0;
|
size_t head = 0;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
struct device_msg msg;
|
struct device_msg msg;
|
||||||
@ -63,7 +53,7 @@ process_msgs(struct receiver *receiver, const unsigned char *buf, size_t len) {
|
|||||||
return head;
|
return head;
|
||||||
}
|
}
|
||||||
|
|
||||||
process_msg(receiver, &msg);
|
process_msg(&msg);
|
||||||
device_msg_destroy(&msg);
|
device_msg_destroy(&msg);
|
||||||
|
|
||||||
head += r;
|
head += r;
|
||||||
@ -91,7 +81,7 @@ run_receiver(void *data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
head += r;
|
head += r;
|
||||||
ssize_t consumed = process_msgs(receiver, buf, head);
|
ssize_t consumed = process_msgs(buf, head);
|
||||||
if (consumed == -1) {
|
if (consumed == -1) {
|
||||||
// an error occurred
|
// an error occurred
|
||||||
break;
|
break;
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
#include "util/acksync.h"
|
|
||||||
#include "util/net.h"
|
#include "util/net.h"
|
||||||
#include "util/thread.h"
|
#include "util/thread.h"
|
||||||
|
|
||||||
@ -15,13 +14,10 @@ struct receiver {
|
|||||||
sc_socket control_socket;
|
sc_socket control_socket;
|
||||||
sc_thread thread;
|
sc_thread thread;
|
||||||
sc_mutex mutex;
|
sc_mutex mutex;
|
||||||
|
|
||||||
struct sc_acksync *acksync;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
receiver_init(struct receiver *receiver, sc_socket control_socket,
|
receiver_init(struct receiver *receiver, sc_socket control_socket);
|
||||||
struct sc_acksync *acksync);
|
|
||||||
|
|
||||||
void
|
void
|
||||||
receiver_destroy(struct receiver *receiver);
|
receiver_destroy(struct receiver *receiver);
|
||||||
|
@ -27,7 +27,6 @@
|
|||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
#include "stream.h"
|
#include "stream.h"
|
||||||
#include "util/acksync.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/net.h"
|
#include "util/net.h"
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
@ -47,8 +46,6 @@ struct scrcpy {
|
|||||||
struct file_handler file_handler;
|
struct file_handler file_handler;
|
||||||
#ifdef HAVE_AOA_HID
|
#ifdef HAVE_AOA_HID
|
||||||
struct sc_aoa aoa;
|
struct sc_aoa aoa;
|
||||||
// sequence/ack helper to synchronize clipboard and Ctrl+v via HID
|
|
||||||
struct sc_acksync acksync;
|
|
||||||
#endif
|
#endif
|
||||||
union {
|
union {
|
||||||
struct sc_keyboard_inject keyboard_inject;
|
struct sc_keyboard_inject keyboard_inject;
|
||||||
@ -343,15 +340,11 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
bool controller_started = false;
|
bool controller_started = false;
|
||||||
bool screen_initialized = false;
|
bool screen_initialized = false;
|
||||||
|
|
||||||
struct sc_acksync *acksync = NULL;
|
|
||||||
|
|
||||||
struct sc_server_params params = {
|
struct sc_server_params params = {
|
||||||
.serial = options->serial,
|
.serial = options->serial,
|
||||||
.log_level = options->log_level,
|
.log_level = options->log_level,
|
||||||
.crop = options->crop,
|
.crop = options->crop,
|
||||||
.port_range = options->port_range,
|
.port_range = options->port_range,
|
||||||
.tunnel_host = options->tunnel_host,
|
|
||||||
.tunnel_port = options->tunnel_port,
|
|
||||||
.max_size = options->max_size,
|
.max_size = options->max_size,
|
||||||
.bit_rate = options->bit_rate,
|
.bit_rate = options->bit_rate,
|
||||||
.max_fps = options->max_fps,
|
.max_fps = options->max_fps,
|
||||||
@ -450,18 +443,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (options->control) {
|
if (options->control) {
|
||||||
#ifdef HAVE_AOA_HID
|
if (!controller_init(&s->controller, s->server.control_socket)) {
|
||||||
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID) {
|
|
||||||
bool ok = sc_acksync_init(&s->acksync);
|
|
||||||
if (!ok) {
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
acksync = &s->acksync;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (!controller_init(&s->controller, s->server.control_socket,
|
|
||||||
acksync)) {
|
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
controller_initialized = true;
|
controller_initialized = true;
|
||||||
@ -537,7 +519,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
#ifdef HAVE_AOA_HID
|
#ifdef HAVE_AOA_HID
|
||||||
bool aoa_hid_ok = false;
|
bool aoa_hid_ok = false;
|
||||||
|
|
||||||
bool ok = sc_aoa_init(&s->aoa, serial, acksync);
|
bool ok = sc_aoa_init(&s->aoa, serial);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
goto aoa_hid_end;
|
goto aoa_hid_end;
|
||||||
}
|
}
|
||||||
@ -602,9 +584,6 @@ end:
|
|||||||
sc_hid_keyboard_destroy(&s->keyboard_hid);
|
sc_hid_keyboard_destroy(&s->keyboard_hid);
|
||||||
sc_aoa_stop(&s->aoa);
|
sc_aoa_stop(&s->aoa);
|
||||||
}
|
}
|
||||||
if (acksync) {
|
|
||||||
sc_acksync_destroy(acksync);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
if (controller_started) {
|
if (controller_started) {
|
||||||
controller_stop(&s->controller);
|
controller_stop(&s->controller);
|
||||||
|
@ -202,9 +202,8 @@ execute_server(struct sc_server *server,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
connect_and_read_byte(struct sc_intr *intr, sc_socket socket,
|
connect_and_read_byte(struct sc_intr *intr, sc_socket socket, uint16_t port) {
|
||||||
uint32_t tunnel_host, uint16_t tunnel_port) {
|
bool ok = net_connect_intr(intr, socket, IPV4_LOCALHOST, port);
|
||||||
bool ok = net_connect_intr(intr, socket, tunnel_host, tunnel_port);
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -221,13 +220,13 @@ connect_and_read_byte(struct sc_intr *intr, sc_socket socket,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static sc_socket
|
static sc_socket
|
||||||
connect_to_server(struct sc_server *server, uint32_t attempts, sc_tick delay,
|
connect_to_server(struct sc_server *server, uint32_t attempts, sc_tick delay) {
|
||||||
uint32_t host, uint16_t port) {
|
uint16_t port = server->tunnel.local_port;
|
||||||
do {
|
do {
|
||||||
LOGD("Remaining connection attempts: %d", (int) attempts);
|
LOGD("Remaining connection attempts: %d", (int) attempts);
|
||||||
sc_socket socket = net_socket();
|
sc_socket socket = net_socket();
|
||||||
if (socket != SC_SOCKET_NONE) {
|
if (socket != SC_SOCKET_NONE) {
|
||||||
bool ok = connect_and_read_byte(&server->intr, socket, host, port);
|
bool ok = connect_and_read_byte(&server->intr, socket, port);
|
||||||
if (ok) {
|
if (ok) {
|
||||||
// it worked!
|
// it worked!
|
||||||
return socket;
|
return socket;
|
||||||
@ -353,20 +352,9 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) {
|
|||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
uint32_t tunnel_host = server->params.tunnel_host;
|
|
||||||
if (!tunnel_host) {
|
|
||||||
tunnel_host = IPV4_LOCALHOST;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t tunnel_port = server->params.tunnel_port;
|
|
||||||
if (!tunnel_port) {
|
|
||||||
tunnel_port = tunnel->local_port;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t attempts = 100;
|
uint32_t attempts = 100;
|
||||||
sc_tick delay = SC_TICK_FROM_MS(100);
|
sc_tick delay = SC_TICK_FROM_MS(100);
|
||||||
video_socket = connect_to_server(server, attempts, delay, tunnel_host,
|
video_socket = connect_to_server(server, attempts, delay);
|
||||||
tunnel_port);
|
|
||||||
if (video_socket == SC_SOCKET_NONE) {
|
if (video_socket == SC_SOCKET_NONE) {
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
@ -376,8 +364,8 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) {
|
|||||||
if (control_socket == SC_SOCKET_NONE) {
|
if (control_socket == SC_SOCKET_NONE) {
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
bool ok = net_connect_intr(&server->intr, control_socket, tunnel_host,
|
bool ok = net_connect_intr(&server->intr, control_socket,
|
||||||
tunnel_port);
|
IPV4_LOCALHOST, tunnel->local_port);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
@ -29,8 +29,6 @@ struct sc_server_params {
|
|||||||
const char *codec_options;
|
const char *codec_options;
|
||||||
const char *encoder_name;
|
const char *encoder_name;
|
||||||
struct sc_port_range port_range;
|
struct sc_port_range port_range;
|
||||||
uint32_t tunnel_host;
|
|
||||||
uint16_t tunnel_port;
|
|
||||||
uint16_t max_size;
|
uint16_t max_size;
|
||||||
uint32_t bit_rate;
|
uint32_t bit_rate;
|
||||||
uint16_t max_fps;
|
uint16_t max_fps;
|
||||||
|
@ -14,31 +14,12 @@
|
|||||||
* Component able to process and inject keys should implement this trait.
|
* Component able to process and inject keys should implement this trait.
|
||||||
*/
|
*/
|
||||||
struct sc_key_processor {
|
struct sc_key_processor {
|
||||||
/**
|
|
||||||
* Set by the implementation to indicate that it must explicitly wait for
|
|
||||||
* the clipboard to be set on the device before injecting Ctrl+v to avoid
|
|
||||||
* race conditions. If it is set, the input_manager will pass a valid
|
|
||||||
* ack_to_wait to process_key() in case of clipboard synchronization
|
|
||||||
* resulting of the key event.
|
|
||||||
*/
|
|
||||||
bool async_paste;
|
|
||||||
|
|
||||||
const struct sc_key_processor_ops *ops;
|
const struct sc_key_processor_ops *ops;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_key_processor_ops {
|
struct sc_key_processor_ops {
|
||||||
|
|
||||||
/**
|
|
||||||
* Process the keyboard event
|
|
||||||
*
|
|
||||||
* The `sequence` number (if different from `SC_SEQUENCE_INVALID`) indicates
|
|
||||||
* the acknowledgement number to wait for before injecting this event.
|
|
||||||
* This allows to ensure that the device clipboard is set before injecting
|
|
||||||
* Ctrl+v on the device.
|
|
||||||
*/
|
|
||||||
void
|
void
|
||||||
(*process_key)(struct sc_key_processor *kp, const SDL_KeyboardEvent *event,
|
(*process_key)(struct sc_key_processor *kp, const SDL_KeyboardEvent *event);
|
||||||
uint64_t ack_to_wait);
|
|
||||||
|
|
||||||
void
|
void
|
||||||
(*process_text)(struct sc_key_processor *kp,
|
(*process_text)(struct sc_key_processor *kp,
|
||||||
|
@ -1,76 +0,0 @@
|
|||||||
#include "acksync.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include "util/log.h"
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_acksync_init(struct sc_acksync *as) {
|
|
||||||
bool ok = sc_mutex_init(&as->mutex);
|
|
||||||
if (!ok) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ok = sc_cond_init(&as->cond);
|
|
||||||
if (!ok) {
|
|
||||||
sc_mutex_destroy(&as->mutex);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
as->stopped = false;
|
|
||||||
as->ack = SC_SEQUENCE_INVALID;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_acksync_destroy(struct sc_acksync *as) {
|
|
||||||
sc_cond_destroy(&as->cond);
|
|
||||||
sc_mutex_destroy(&as->mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_acksync_ack(struct sc_acksync *as, uint64_t sequence) {
|
|
||||||
sc_mutex_lock(&as->mutex);
|
|
||||||
|
|
||||||
// Acknowledgements must be monotonic
|
|
||||||
assert(sequence >= as->ack);
|
|
||||||
|
|
||||||
as->ack = sequence;
|
|
||||||
sc_cond_signal(&as->cond);
|
|
||||||
|
|
||||||
sc_mutex_unlock(&as->mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum sc_acksync_wait_result
|
|
||||||
sc_acksync_wait(struct sc_acksync *as, uint64_t ack, sc_tick deadline) {
|
|
||||||
sc_mutex_lock(&as->mutex);
|
|
||||||
|
|
||||||
bool timed_out = false;
|
|
||||||
while (!as->stopped && as->ack < ack && !timed_out) {
|
|
||||||
timed_out = !sc_cond_timedwait(&as->cond, &as->mutex, deadline);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum sc_acksync_wait_result ret;
|
|
||||||
if (as->stopped) {
|
|
||||||
ret = SC_ACKSYNC_WAIT_INTR;
|
|
||||||
} else if (as->ack >= ack) {
|
|
||||||
ret = SC_ACKSYNC_WAIT_OK;
|
|
||||||
} else {
|
|
||||||
assert(timed_out);
|
|
||||||
ret = SC_ACKSYNC_WAIT_TIMEOUT;
|
|
||||||
}
|
|
||||||
sc_mutex_unlock(&as->mutex);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interrupt any `sc_acksync_wait()`
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
sc_acksync_interrupt(struct sc_acksync *as) {
|
|
||||||
sc_mutex_lock(&as->mutex);
|
|
||||||
as->stopped = true;
|
|
||||||
sc_cond_signal(&as->cond);
|
|
||||||
sc_mutex_unlock(&as->mutex);
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
#ifndef SC_ACK_SYNC_H
|
|
||||||
#define SC_ACK_SYNC_H
|
|
||||||
|
|
||||||
#include "common.h"
|
|
||||||
|
|
||||||
#include "thread.h"
|
|
||||||
|
|
||||||
#define SC_SEQUENCE_INVALID 0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper to wait for acknowledgments
|
|
||||||
*
|
|
||||||
* In practice, it is used to wait for device clipboard acknowledgement from the
|
|
||||||
* server before injecting Ctrl+v via AOA HID, in order to avoid pasting the
|
|
||||||
* content of the old device clipboard (if Ctrl+v was injected before the
|
|
||||||
* clipboard content was actually set).
|
|
||||||
*/
|
|
||||||
struct sc_acksync {
|
|
||||||
sc_mutex mutex;
|
|
||||||
sc_cond cond;
|
|
||||||
|
|
||||||
bool stopped;
|
|
||||||
|
|
||||||
// Last acked value, initially SC_SEQUENCE_INVALID
|
|
||||||
uint64_t ack;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum sc_acksync_wait_result {
|
|
||||||
// Acknowledgment received
|
|
||||||
SC_ACKSYNC_WAIT_OK,
|
|
||||||
|
|
||||||
// Timeout expired
|
|
||||||
SC_ACKSYNC_WAIT_TIMEOUT,
|
|
||||||
|
|
||||||
// Interrupted from another thread by sc_acksync_interrupt()
|
|
||||||
SC_ACKSYNC_WAIT_INTR,
|
|
||||||
};
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_acksync_init(struct sc_acksync *as);
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_acksync_destroy(struct sc_acksync *as);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Acknowledge `sequence`
|
|
||||||
*
|
|
||||||
* The `sequence` must be greater than (or equal to) any previous acknowledged
|
|
||||||
* sequence.
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
sc_acksync_ack(struct sc_acksync *as, uint64_t sequence);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wait for acknowledgment of sequence `ack` (or higher)
|
|
||||||
*/
|
|
||||||
enum sc_acksync_wait_result
|
|
||||||
sc_acksync_wait(struct sc_acksync *as, uint64_t ack, sc_tick deadline);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interrupt any `sc_acksync_wait()`
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
sc_acksync_interrupt(struct sc_acksync *as);
|
|
||||||
|
|
||||||
#endif
|
|
@ -7,7 +7,6 @@
|
|||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
#ifdef __WINDOWS__
|
#ifdef __WINDOWS__
|
||||||
# include <ws2tcpip.h>
|
|
||||||
typedef int socklen_t;
|
typedef int socklen_t;
|
||||||
typedef SOCKET sc_raw_socket;
|
typedef SOCKET sc_raw_socket;
|
||||||
#else
|
#else
|
||||||
@ -226,15 +225,3 @@ net_close(sc_socket socket) {
|
|||||||
return !close(raw_sock);
|
return !close(raw_sock);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
|
||||||
net_parse_ipv4(const char *s, uint32_t *ipv4) {
|
|
||||||
struct in_addr addr;
|
|
||||||
if (!inet_pton(AF_INET, s, &addr)) {
|
|
||||||
LOGE("Invalid IPv4 address: %s", s);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
*ipv4 = ntohl(addr.s_addr);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
@ -68,10 +68,4 @@ net_interrupt(sc_socket socket);
|
|||||||
bool
|
bool
|
||||||
net_close(sc_socket socket);
|
net_close(sc_socket socket);
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse `ip` "xxx.xxx.xxx.xxx" to an IPv4 host representation
|
|
||||||
*/
|
|
||||||
bool
|
|
||||||
net_parse_ipv4(const char *ip, uint32_t *ipv4);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -78,7 +78,7 @@ static void test_serialize_inject_touch_event(void) {
|
|||||||
.type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
|
.type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
|
||||||
.inject_touch_event = {
|
.inject_touch_event = {
|
||||||
.action = AMOTION_EVENT_ACTION_DOWN,
|
.action = AMOTION_EVENT_ACTION_DOWN,
|
||||||
.pointer_id = UINT64_C(0x1234567887654321),
|
.pointer_id = 0x1234567887654321L,
|
||||||
.position = {
|
.position = {
|
||||||
.point = {
|
.point = {
|
||||||
.x = 100,
|
.x = 100,
|
||||||
@ -226,7 +226,6 @@ static void test_serialize_set_clipboard(void) {
|
|||||||
struct control_msg msg = {
|
struct control_msg msg = {
|
||||||
.type = CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
.type = CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
||||||
.set_clipboard = {
|
.set_clipboard = {
|
||||||
.sequence = UINT64_C(0x0102030405060708),
|
|
||||||
.paste = true,
|
.paste = true,
|
||||||
.text = "hello, world!",
|
.text = "hello, world!",
|
||||||
},
|
},
|
||||||
@ -234,11 +233,10 @@ static void test_serialize_set_clipboard(void) {
|
|||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
size_t size = control_msg_serialize(&msg, buf);
|
size_t size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 27);
|
assert(size == 19);
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
const unsigned char expected[] = {
|
||||||
CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
||||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // sequence
|
|
||||||
1, // paste
|
1, // paste
|
||||||
0x00, 0x00, 0x00, 0x0d, // text length
|
0x00, 0x00, 0x00, 0x0d, // text length
|
||||||
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text
|
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text
|
||||||
|
@ -47,26 +47,11 @@ static void test_deserialize_clipboard_big(void) {
|
|||||||
device_msg_destroy(&msg);
|
device_msg_destroy(&msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_deserialize_ack_set_clipboard(void) {
|
|
||||||
const unsigned char input[] = {
|
|
||||||
DEVICE_MSG_TYPE_ACK_CLIPBOARD,
|
|
||||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // sequence
|
|
||||||
};
|
|
||||||
|
|
||||||
struct device_msg msg;
|
|
||||||
ssize_t r = device_msg_deserialize(input, sizeof(input), &msg);
|
|
||||||
assert(r == 9);
|
|
||||||
|
|
||||||
assert(msg.type == DEVICE_MSG_TYPE_ACK_CLIPBOARD);
|
|
||||||
assert(msg.ack_clipboard.sequence == UINT64_C(0x0102030405060708));
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
(void) argc;
|
(void) argc;
|
||||||
(void) argv;
|
(void) argv;
|
||||||
|
|
||||||
test_deserialize_clipboard();
|
test_deserialize_clipboard();
|
||||||
test_deserialize_clipboard_big();
|
test_deserialize_clipboard_big();
|
||||||
test_deserialize_ack_set_clipboard();
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -18,8 +18,6 @@ public final class ControlMessage {
|
|||||||
public static final int TYPE_SET_SCREEN_POWER_MODE = 10;
|
public static final int TYPE_SET_SCREEN_POWER_MODE = 10;
|
||||||
public static final int TYPE_ROTATE_DEVICE = 11;
|
public static final int TYPE_ROTATE_DEVICE = 11;
|
||||||
|
|
||||||
public static final long SEQUENCE_INVALID = 0;
|
|
||||||
|
|
||||||
private int type;
|
private int type;
|
||||||
private String text;
|
private String text;
|
||||||
private int metaState; // KeyEvent.META_*
|
private int metaState; // KeyEvent.META_*
|
||||||
@ -33,7 +31,6 @@ public final class ControlMessage {
|
|||||||
private int vScroll;
|
private int vScroll;
|
||||||
private boolean paste;
|
private boolean paste;
|
||||||
private int repeat;
|
private int repeat;
|
||||||
private long sequence;
|
|
||||||
|
|
||||||
private ControlMessage() {
|
private ControlMessage() {
|
||||||
}
|
}
|
||||||
@ -82,10 +79,9 @@ public final class ControlMessage {
|
|||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ControlMessage createSetClipboard(long sequence, String text, boolean paste) {
|
public static ControlMessage createSetClipboard(String text, boolean paste) {
|
||||||
ControlMessage msg = new ControlMessage();
|
ControlMessage msg = new ControlMessage();
|
||||||
msg.type = TYPE_SET_CLIPBOARD;
|
msg.type = TYPE_SET_CLIPBOARD;
|
||||||
msg.sequence = sequence;
|
|
||||||
msg.text = text;
|
msg.text = text;
|
||||||
msg.paste = paste;
|
msg.paste = paste;
|
||||||
return msg;
|
return msg;
|
||||||
@ -158,8 +154,4 @@ public final class ControlMessage {
|
|||||||
public int getRepeat() {
|
public int getRepeat() {
|
||||||
return repeat;
|
return repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getSequence() {
|
|
||||||
return sequence;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -13,11 +13,11 @@ public class ControlMessageReader {
|
|||||||
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
||||||
static final int BACK_OR_SCREEN_ON_LENGTH = 1;
|
static final int BACK_OR_SCREEN_ON_LENGTH = 1;
|
||||||
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
|
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
|
||||||
static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 9;
|
static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 1;
|
||||||
|
|
||||||
private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k
|
private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k
|
||||||
|
|
||||||
public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 14; // type: 1 byte; sequence: 8 bytes; paste flag: 1 byte; length: 4 bytes
|
public static final int CLIPBOARD_TEXT_MAX_LENGTH = MESSAGE_MAX_SIZE - 6; // type: 1 byte; paste flag: 1 byte; length: 4 bytes
|
||||||
public static final int INJECT_TEXT_MAX_LENGTH = 300;
|
public static final int INJECT_TEXT_MAX_LENGTH = 300;
|
||||||
|
|
||||||
private final byte[] rawBuffer = new byte[MESSAGE_MAX_SIZE];
|
private final byte[] rawBuffer = new byte[MESSAGE_MAX_SIZE];
|
||||||
@ -166,13 +166,12 @@ public class ControlMessageReader {
|
|||||||
if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) {
|
if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
long sequence = buffer.getLong();
|
|
||||||
boolean paste = buffer.get() != 0;
|
boolean paste = buffer.get() != 0;
|
||||||
String text = parseString();
|
String text = parseString();
|
||||||
if (text == null) {
|
if (text == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return ControlMessage.createSetClipboard(sequence, text, paste);
|
return ControlMessage.createSetClipboard(text, paste);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ControlMessage parseSetScreenPowerMode() {
|
private ControlMessage parseSetScreenPowerMode() {
|
||||||
|
@ -120,12 +120,7 @@ public class Controller {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_SET_CLIPBOARD:
|
case ControlMessage.TYPE_SET_CLIPBOARD:
|
||||||
long sequence = msg.getSequence();
|
|
||||||
setClipboard(msg.getText(), msg.getPaste());
|
setClipboard(msg.getText(), msg.getPaste());
|
||||||
if (sequence != ControlMessage.SEQUENCE_INVALID) {
|
|
||||||
// Acknowledgement requested
|
|
||||||
sender.pushAckClipboard(sequence);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
|
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
|
||||||
if (device.supportsInputEvents()) {
|
if (device.supportsInputEvents()) {
|
||||||
|
@ -3,13 +3,9 @@ package com.genymobile.scrcpy;
|
|||||||
public final class DeviceMessage {
|
public final class DeviceMessage {
|
||||||
|
|
||||||
public static final int TYPE_CLIPBOARD = 0;
|
public static final int TYPE_CLIPBOARD = 0;
|
||||||
public static final int TYPE_ACK_CLIPBOARD = 1;
|
|
||||||
|
|
||||||
public static final long SEQUENCE_INVALID = ControlMessage.SEQUENCE_INVALID;
|
|
||||||
|
|
||||||
private int type;
|
private int type;
|
||||||
private String text;
|
private String text;
|
||||||
private long sequence;
|
|
||||||
|
|
||||||
private DeviceMessage() {
|
private DeviceMessage() {
|
||||||
}
|
}
|
||||||
@ -21,13 +17,6 @@ public final class DeviceMessage {
|
|||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DeviceMessage createAckClipboard(long sequence) {
|
|
||||||
DeviceMessage event = new DeviceMessage();
|
|
||||||
event.type = TYPE_ACK_CLIPBOARD;
|
|
||||||
event.sequence = sequence;
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getType() {
|
public int getType() {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
@ -35,8 +24,4 @@ public final class DeviceMessage {
|
|||||||
public String getText() {
|
public String getText() {
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getSequence() {
|
|
||||||
return sequence;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,6 @@ public final class DeviceMessageSender {
|
|||||||
|
|
||||||
private String clipboardText;
|
private String clipboardText;
|
||||||
|
|
||||||
private long ack;
|
|
||||||
|
|
||||||
public DeviceMessageSender(DesktopConnection connection) {
|
public DeviceMessageSender(DesktopConnection connection) {
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
}
|
}
|
||||||
@ -19,34 +17,18 @@ public final class DeviceMessageSender {
|
|||||||
notify();
|
notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void pushAckClipboard(long sequence) {
|
|
||||||
ack = sequence;
|
|
||||||
notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void loop() throws IOException, InterruptedException {
|
public void loop() throws IOException, InterruptedException {
|
||||||
while (true) {
|
while (true) {
|
||||||
String text;
|
String text;
|
||||||
long sequence;
|
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
while (ack == DeviceMessage.SEQUENCE_INVALID && clipboardText == null) {
|
while (clipboardText == null) {
|
||||||
wait();
|
wait();
|
||||||
}
|
}
|
||||||
text = clipboardText;
|
text = clipboardText;
|
||||||
clipboardText = null;
|
clipboardText = null;
|
||||||
|
|
||||||
sequence = ack;
|
|
||||||
ack = DeviceMessage.SEQUENCE_INVALID;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sequence != DeviceMessage.SEQUENCE_INVALID) {
|
|
||||||
DeviceMessage event = DeviceMessage.createAckClipboard(sequence);
|
|
||||||
connection.sendDeviceMessage(event);
|
|
||||||
}
|
|
||||||
if (text != null) {
|
|
||||||
DeviceMessage event = DeviceMessage.createClipboard(text);
|
DeviceMessage event = DeviceMessage.createClipboard(text);
|
||||||
connection.sendDeviceMessage(event);
|
connection.sendDeviceMessage(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -15,7 +15,7 @@ public class DeviceMessageWriter {
|
|||||||
|
|
||||||
public void writeTo(DeviceMessage msg, OutputStream output) throws IOException {
|
public void writeTo(DeviceMessage msg, OutputStream output) throws IOException {
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
buffer.put((byte) msg.getType());
|
buffer.put((byte) DeviceMessage.TYPE_CLIPBOARD);
|
||||||
switch (msg.getType()) {
|
switch (msg.getType()) {
|
||||||
case DeviceMessage.TYPE_CLIPBOARD:
|
case DeviceMessage.TYPE_CLIPBOARD:
|
||||||
String text = msg.getText();
|
String text = msg.getText();
|
||||||
@ -25,10 +25,6 @@ public class DeviceMessageWriter {
|
|||||||
buffer.put(raw, 0, len);
|
buffer.put(raw, 0, len);
|
||||||
output.write(rawBuffer, 0, buffer.position());
|
output.write(rawBuffer, 0, buffer.position());
|
||||||
break;
|
break;
|
||||||
case DeviceMessage.TYPE_ACK_CLIPBOARD:
|
|
||||||
buffer.putLong(msg.getSequence());
|
|
||||||
output.write(rawBuffer, 0, buffer.position());
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
Ln.w("Unknown device message: " + msg.getType());
|
Ln.w("Unknown device message: " + msg.getType());
|
||||||
break;
|
break;
|
||||||
|
@ -235,7 +235,6 @@ public class ControlMessageReaderTest {
|
|||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
|
dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
|
||||||
dos.writeLong(0x0102030405060708L); // sequence
|
|
||||||
dos.writeByte(1); // paste
|
dos.writeByte(1); // paste
|
||||||
byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
|
byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
|
||||||
dos.writeInt(text.length);
|
dos.writeInt(text.length);
|
||||||
@ -247,7 +246,6 @@ public class ControlMessageReaderTest {
|
|||||||
ControlMessage event = reader.next();
|
ControlMessage event = reader.next();
|
||||||
|
|
||||||
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
|
||||||
Assert.assertEquals(0x0102030405060708L, event.getSequence());
|
|
||||||
Assert.assertEquals("testé", event.getText());
|
Assert.assertEquals("testé", event.getText());
|
||||||
Assert.assertTrue(event.getPaste());
|
Assert.assertTrue(event.getPaste());
|
||||||
}
|
}
|
||||||
@ -261,7 +259,6 @@ public class ControlMessageReaderTest {
|
|||||||
dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
|
dos.writeByte(ControlMessage.TYPE_SET_CLIPBOARD);
|
||||||
|
|
||||||
byte[] rawText = new byte[ControlMessageReader.CLIPBOARD_TEXT_MAX_LENGTH];
|
byte[] rawText = new byte[ControlMessageReader.CLIPBOARD_TEXT_MAX_LENGTH];
|
||||||
dos.writeLong(0x0807060504030201L); // sequence
|
|
||||||
dos.writeByte(1); // paste
|
dos.writeByte(1); // paste
|
||||||
Arrays.fill(rawText, (byte) 'a');
|
Arrays.fill(rawText, (byte) 'a');
|
||||||
String text = new String(rawText, 0, rawText.length);
|
String text = new String(rawText, 0, rawText.length);
|
||||||
@ -275,7 +272,6 @@ public class ControlMessageReaderTest {
|
|||||||
ControlMessage event = reader.next();
|
ControlMessage event = reader.next();
|
||||||
|
|
||||||
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
|
Assert.assertEquals(ControlMessage.TYPE_SET_CLIPBOARD, event.getType());
|
||||||
Assert.assertEquals(0x0807060504030201L, event.getSequence());
|
|
||||||
Assert.assertEquals(text, event.getText());
|
Assert.assertEquals(text, event.getText());
|
||||||
Assert.assertTrue(event.getPaste());
|
Assert.assertTrue(event.getPaste());
|
||||||
}
|
}
|
||||||
|
@ -32,24 +32,4 @@ public class DeviceMessageWriterTest {
|
|||||||
|
|
||||||
Assert.assertArrayEquals(expected, actual);
|
Assert.assertArrayEquals(expected, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSerializeAckSetClipboard() throws IOException {
|
|
||||||
DeviceMessageWriter writer = new DeviceMessageWriter();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
|
||||||
dos.writeByte(DeviceMessage.TYPE_ACK_CLIPBOARD);
|
|
||||||
dos.writeLong(0x0102030405060708L);
|
|
||||||
|
|
||||||
byte[] expected = bos.toByteArray();
|
|
||||||
|
|
||||||
DeviceMessage msg = DeviceMessage.createAckClipboard(0x0102030405060708L);
|
|
||||||
bos = new ByteArrayOutputStream();
|
|
||||||
writer.writeTo(msg, bos);
|
|
||||||
|
|
||||||
byte[] actual = bos.toByteArray();
|
|
||||||
|
|
||||||
Assert.assertArrayEquals(expected, actual);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user