2019-05-30 00:25:37 +02:00
|
|
|
#include "receiver.h"
|
|
|
|
|
|
2019-11-27 21:11:40 +01:00
|
|
|
#include <assert.h>
|
2024-01-12 23:32:30 +08:00
|
|
|
#include <inttypes.h>
|
2025-07-11 09:42:03 +02:00
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <SDL3/SDL_clipboard.h>
|
2019-05-30 00:25:37 +02:00
|
|
|
|
2019-05-31 14:55:11 +02:00
|
|
|
#include "device_msg.h"
|
2024-09-06 23:08:08 +02:00
|
|
|
#include "events.h"
|
2019-11-24 11:53:00 +01:00
|
|
|
#include "util/log.h"
|
2024-01-12 23:32:30 +08:00
|
|
|
#include "util/str.h"
|
2024-09-06 23:08:08 +02:00
|
|
|
#include "util/thread.h"
|
2019-05-30 00:25:37 +02:00
|
|
|
|
2024-09-06 23:08:08 +02:00
|
|
|
struct sc_uhid_output_task_data {
|
|
|
|
|
struct sc_uhid_devices *uhid_devices;
|
|
|
|
|
uint16_t id;
|
|
|
|
|
uint16_t size;
|
|
|
|
|
uint8_t *data;
|
|
|
|
|
};
|
|
|
|
|
|
2019-05-30 00:25:37 +02:00
|
|
|
bool
|
2024-05-11 16:40:22 +02:00
|
|
|
sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket,
|
|
|
|
|
const struct sc_receiver_callbacks *cbs, void *cbs_userdata) {
|
2021-01-31 18:24:35 +01:00
|
|
|
bool ok = sc_mutex_init(&receiver->mutex);
|
|
|
|
|
if (!ok) {
|
2019-05-30 00:25:37 +02:00
|
|
|
return false;
|
|
|
|
|
}
|
Wait SET_CLIPBOARD ack before Ctrl+v via HID
To allow seamless copy-paste, on Ctrl+v, a SET_CLIPBOARD request is
performed before injecting Ctrl+v.
But when HID keyboard is enabled, the Ctrl+v injection is not sent on
the same channel as the clipboard request, so they are not serialized,
and may occur in any order. If Ctrl+v happens to be injected before the
new clipboard content is set, then the old content is pasted instead,
which is incorrect.
To minimize the probability of occurrence of the wrong order, a delay of
2 milliseconds was added before injecting Ctrl+v. Then 5ms. But even
with 5ms, the wrong behavior sometimes happens.
To handle it properly, add an acknowledgement mechanism, so that Ctrl+v
is injected over AOA only after the SET_CLIPBOARD request has been
performed and acknowledged by the server.
Refs e4163321f00bb3830c6049bdb6c1515e7cc668a0
Refs 45b0f8123a52f5c73a5860d616f4ceba2766ca6a
PR #2814 <https://github.com/Genymobile/scrcpy/pull/2814>
2021-11-21 17:40:11 +01:00
|
|
|
|
2019-05-30 00:25:37 +02:00
|
|
|
receiver->control_socket = control_socket;
|
2024-02-24 22:33:48 +01:00
|
|
|
receiver->acksync = NULL;
|
2024-01-12 23:32:30 +08:00
|
|
|
receiver->uhid_devices = NULL;
|
Wait SET_CLIPBOARD ack before Ctrl+v via HID
To allow seamless copy-paste, on Ctrl+v, a SET_CLIPBOARD request is
performed before injecting Ctrl+v.
But when HID keyboard is enabled, the Ctrl+v injection is not sent on
the same channel as the clipboard request, so they are not serialized,
and may occur in any order. If Ctrl+v happens to be injected before the
new clipboard content is set, then the old content is pasted instead,
which is incorrect.
To minimize the probability of occurrence of the wrong order, a delay of
2 milliseconds was added before injecting Ctrl+v. Then 5ms. But even
with 5ms, the wrong behavior sometimes happens.
To handle it properly, add an acknowledgement mechanism, so that Ctrl+v
is injected over AOA only after the SET_CLIPBOARD request has been
performed and acknowledged by the server.
Refs e4163321f00bb3830c6049bdb6c1515e7cc668a0
Refs 45b0f8123a52f5c73a5860d616f4ceba2766ca6a
PR #2814 <https://github.com/Genymobile/scrcpy/pull/2814>
2021-11-21 17:40:11 +01:00
|
|
|
|
2024-07-01 08:17:33 +02:00
|
|
|
assert(cbs && cbs->on_ended);
|
2024-05-11 16:40:22 +02:00
|
|
|
receiver->cbs = cbs;
|
|
|
|
|
receiver->cbs_userdata = cbs_userdata;
|
|
|
|
|
|
2019-05-30 00:25:37 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
2023-02-18 09:37:13 +01:00
|
|
|
sc_receiver_destroy(struct sc_receiver *receiver) {
|
2021-01-31 18:24:35 +01:00
|
|
|
sc_mutex_destroy(&receiver->mutex);
|
2019-05-30 00:25:37 +02:00
|
|
|
}
|
|
|
|
|
|
2024-09-06 23:08:08 +02:00
|
|
|
static void
|
|
|
|
|
task_set_clipboard(void *userdata) {
|
|
|
|
|
assert(sc_thread_get_id() == SC_MAIN_THREAD_ID);
|
|
|
|
|
|
|
|
|
|
char *text = userdata;
|
|
|
|
|
|
|
|
|
|
char *current = SDL_GetClipboardText();
|
|
|
|
|
bool same = current && !strcmp(current, text);
|
|
|
|
|
SDL_free(current);
|
|
|
|
|
if (same) {
|
|
|
|
|
LOGD("Computer clipboard unchanged");
|
|
|
|
|
} else {
|
2025-07-11 09:42:03 +02:00
|
|
|
bool ok = SDL_SetClipboardText(text);
|
|
|
|
|
if (ok) {
|
|
|
|
|
LOGI("Device clipboard copied");
|
|
|
|
|
} else {
|
|
|
|
|
LOGE("Could not set clipboard: %s", SDL_GetError());
|
|
|
|
|
}
|
2024-09-06 23:08:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
free(text);
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-06 23:08:08 +02:00
|
|
|
static void
|
|
|
|
|
task_uhid_output(void *userdata) {
|
|
|
|
|
assert(sc_thread_get_id() == SC_MAIN_THREAD_ID);
|
|
|
|
|
|
|
|
|
|
struct sc_uhid_output_task_data *data = userdata;
|
|
|
|
|
|
2024-09-06 23:08:08 +02:00
|
|
|
sc_uhid_devices_process_hid_output(data->uhid_devices, data->id, data->data,
|
|
|
|
|
data->size);
|
2024-09-06 23:08:08 +02:00
|
|
|
|
|
|
|
|
free(data->data);
|
|
|
|
|
free(data);
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-30 00:25:37 +02:00
|
|
|
static void
|
2024-02-23 20:05:12 +01:00
|
|
|
process_msg(struct sc_receiver *receiver, struct sc_device_msg *msg) {
|
2019-05-31 14:55:11 +02:00
|
|
|
switch (msg->type) {
|
2020-07-17 00:00:42 +02:00
|
|
|
case DEVICE_MSG_TYPE_CLIPBOARD: {
|
2024-09-06 23:08:08 +02:00
|
|
|
// Take ownership of the text (do not destroy the msg)
|
|
|
|
|
char *text = msg->clipboard.text;
|
|
|
|
|
|
|
|
|
|
bool ok = sc_post_to_main_thread(task_set_clipboard, text);
|
|
|
|
|
if (!ok) {
|
|
|
|
|
LOGW("Could not post clipboard to main thread");
|
|
|
|
|
free(text);
|
2020-07-17 00:00:42 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-30 00:25:37 +02:00
|
|
|
break;
|
2020-07-17 00:00:42 +02:00
|
|
|
}
|
2021-11-20 12:10:09 +01:00
|
|
|
case DEVICE_MSG_TYPE_ACK_CLIPBOARD:
|
Wait SET_CLIPBOARD ack before Ctrl+v via HID
To allow seamless copy-paste, on Ctrl+v, a SET_CLIPBOARD request is
performed before injecting Ctrl+v.
But when HID keyboard is enabled, the Ctrl+v injection is not sent on
the same channel as the clipboard request, so they are not serialized,
and may occur in any order. If Ctrl+v happens to be injected before the
new clipboard content is set, then the old content is pasted instead,
which is incorrect.
To minimize the probability of occurrence of the wrong order, a delay of
2 milliseconds was added before injecting Ctrl+v. Then 5ms. But even
with 5ms, the wrong behavior sometimes happens.
To handle it properly, add an acknowledgement mechanism, so that Ctrl+v
is injected over AOA only after the SET_CLIPBOARD request has been
performed and acknowledged by the server.
Refs e4163321f00bb3830c6049bdb6c1515e7cc668a0
Refs 45b0f8123a52f5c73a5860d616f4ceba2766ca6a
PR #2814 <https://github.com/Genymobile/scrcpy/pull/2814>
2021-11-21 17:40:11 +01:00
|
|
|
LOGD("Ack device clipboard sequence=%" PRIu64_,
|
|
|
|
|
msg->ack_clipboard.sequence);
|
2024-02-27 09:59:23 +01:00
|
|
|
|
|
|
|
|
// This is a programming error to receive this message if there is
|
|
|
|
|
// no ACK synchronization mechanism
|
|
|
|
|
assert(receiver->acksync);
|
|
|
|
|
|
|
|
|
|
// Also check at runtime (do not trust the server)
|
|
|
|
|
if (!receiver->acksync) {
|
|
|
|
|
LOGE("Received unexpected ack");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
Wait SET_CLIPBOARD ack before Ctrl+v via HID
To allow seamless copy-paste, on Ctrl+v, a SET_CLIPBOARD request is
performed before injecting Ctrl+v.
But when HID keyboard is enabled, the Ctrl+v injection is not sent on
the same channel as the clipboard request, so they are not serialized,
and may occur in any order. If Ctrl+v happens to be injected before the
new clipboard content is set, then the old content is pasted instead,
which is incorrect.
To minimize the probability of occurrence of the wrong order, a delay of
2 milliseconds was added before injecting Ctrl+v. Then 5ms. But even
with 5ms, the wrong behavior sometimes happens.
To handle it properly, add an acknowledgement mechanism, so that Ctrl+v
is injected over AOA only after the SET_CLIPBOARD request has been
performed and acknowledged by the server.
Refs e4163321f00bb3830c6049bdb6c1515e7cc668a0
Refs 45b0f8123a52f5c73a5860d616f4ceba2766ca6a
PR #2814 <https://github.com/Genymobile/scrcpy/pull/2814>
2021-11-21 17:40:11 +01:00
|
|
|
sc_acksync_ack(receiver->acksync, msg->ack_clipboard.sequence);
|
2024-09-06 23:08:08 +02:00
|
|
|
// No allocation to free in the msg
|
2021-11-20 12:10:09 +01:00
|
|
|
break;
|
2024-01-12 23:32:30 +08:00
|
|
|
case DEVICE_MSG_TYPE_UHID_OUTPUT:
|
|
|
|
|
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
|
|
|
|
|
char *hex = sc_str_to_hex_string(msg->uhid_output.data,
|
|
|
|
|
msg->uhid_output.size);
|
|
|
|
|
if (hex) {
|
|
|
|
|
LOGV("UHID output [%" PRIu16 "] %s",
|
|
|
|
|
msg->uhid_output.id, hex);
|
|
|
|
|
free(hex);
|
|
|
|
|
} else {
|
|
|
|
|
LOGV("UHID output [%" PRIu16 "] size=%" PRIu16,
|
|
|
|
|
msg->uhid_output.id, msg->uhid_output.size);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!receiver->uhid_devices) {
|
|
|
|
|
LOGE("Received unexpected HID output message");
|
2024-09-06 23:08:08 +02:00
|
|
|
sc_device_msg_destroy(msg);
|
2024-01-12 23:32:30 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-06 23:08:08 +02:00
|
|
|
struct sc_uhid_output_task_data *data = malloc(sizeof(*data));
|
|
|
|
|
if (!data) {
|
|
|
|
|
LOG_OOM();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// It is guaranteed that these pointers will still be valid when
|
|
|
|
|
// the main thread will process them (the main thread will stop
|
|
|
|
|
// processing SC_EVENT_RUN_ON_MAIN_THREAD on exit, when everything
|
|
|
|
|
// gets deinitialized)
|
|
|
|
|
data->uhid_devices = receiver->uhid_devices;
|
|
|
|
|
data->id = msg->uhid_output.id;
|
|
|
|
|
data->data = msg->uhid_output.data; // take ownership
|
|
|
|
|
data->size = msg->uhid_output.size;
|
|
|
|
|
|
|
|
|
|
bool ok = sc_post_to_main_thread(task_uhid_output, data);
|
|
|
|
|
if (!ok) {
|
|
|
|
|
LOGW("Could not post UHID output to main thread");
|
|
|
|
|
free(data->data);
|
|
|
|
|
free(data);
|
|
|
|
|
return;
|
2024-01-12 23:32:30 +08:00
|
|
|
}
|
2024-09-06 23:08:08 +02:00
|
|
|
|
2024-01-12 23:32:30 +08:00
|
|
|
break;
|
2019-05-30 00:25:37 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static ssize_t
|
2024-02-23 19:59:54 +01:00
|
|
|
process_msgs(struct sc_receiver *receiver, const uint8_t *buf, size_t len) {
|
2019-05-30 00:25:37 +02:00
|
|
|
size_t head = 0;
|
|
|
|
|
for (;;) {
|
2024-02-23 20:05:12 +01:00
|
|
|
struct sc_device_msg msg;
|
|
|
|
|
ssize_t r = sc_device_msg_deserialize(&buf[head], len - head, &msg);
|
2019-05-30 00:25:37 +02:00
|
|
|
if (r == -1) {
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
if (r == 0) {
|
|
|
|
|
return head;
|
|
|
|
|
}
|
|
|
|
|
|
Wait SET_CLIPBOARD ack before Ctrl+v via HID
To allow seamless copy-paste, on Ctrl+v, a SET_CLIPBOARD request is
performed before injecting Ctrl+v.
But when HID keyboard is enabled, the Ctrl+v injection is not sent on
the same channel as the clipboard request, so they are not serialized,
and may occur in any order. If Ctrl+v happens to be injected before the
new clipboard content is set, then the old content is pasted instead,
which is incorrect.
To minimize the probability of occurrence of the wrong order, a delay of
2 milliseconds was added before injecting Ctrl+v. Then 5ms. But even
with 5ms, the wrong behavior sometimes happens.
To handle it properly, add an acknowledgement mechanism, so that Ctrl+v
is injected over AOA only after the SET_CLIPBOARD request has been
performed and acknowledged by the server.
Refs e4163321f00bb3830c6049bdb6c1515e7cc668a0
Refs 45b0f8123a52f5c73a5860d616f4ceba2766ca6a
PR #2814 <https://github.com/Genymobile/scrcpy/pull/2814>
2021-11-21 17:40:11 +01:00
|
|
|
process_msg(receiver, &msg);
|
2024-09-06 23:08:08 +02:00
|
|
|
// the device msg must be destroyed by process_msg()
|
2019-05-30 00:25:37 +02:00
|
|
|
|
|
|
|
|
head += r;
|
2019-11-27 21:11:40 +01:00
|
|
|
assert(head <= len);
|
2019-05-30 00:25:37 +02:00
|
|
|
if (head == len) {
|
|
|
|
|
return head;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
run_receiver(void *data) {
|
2023-02-18 09:37:13 +01:00
|
|
|
struct sc_receiver *receiver = data;
|
2019-05-30 00:25:37 +02:00
|
|
|
|
2024-02-23 19:59:54 +01:00
|
|
|
static uint8_t buf[DEVICE_MSG_MAX_SIZE];
|
2019-05-30 00:25:37 +02:00
|
|
|
size_t head = 0;
|
|
|
|
|
|
2024-07-01 08:17:33 +02:00
|
|
|
bool error = false;
|
|
|
|
|
|
2019-05-30 00:25:37 +02:00
|
|
|
for (;;) {
|
2020-06-04 21:26:38 +02:00
|
|
|
assert(head < DEVICE_MSG_MAX_SIZE);
|
2020-06-04 22:44:43 +02:00
|
|
|
ssize_t r = net_recv(receiver->control_socket, buf + head,
|
2020-06-04 21:26:38 +02:00
|
|
|
DEVICE_MSG_MAX_SIZE - head);
|
2019-05-30 00:25:37 +02:00
|
|
|
if (r <= 0) {
|
|
|
|
|
LOGD("Receiver stopped");
|
2024-07-01 08:17:33 +02:00
|
|
|
// device disconnected: keep error=false
|
2019-05-30 00:25:37 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-04 22:44:43 +02:00
|
|
|
head += r;
|
Wait SET_CLIPBOARD ack before Ctrl+v via HID
To allow seamless copy-paste, on Ctrl+v, a SET_CLIPBOARD request is
performed before injecting Ctrl+v.
But when HID keyboard is enabled, the Ctrl+v injection is not sent on
the same channel as the clipboard request, so they are not serialized,
and may occur in any order. If Ctrl+v happens to be injected before the
new clipboard content is set, then the old content is pasted instead,
which is incorrect.
To minimize the probability of occurrence of the wrong order, a delay of
2 milliseconds was added before injecting Ctrl+v. Then 5ms. But even
with 5ms, the wrong behavior sometimes happens.
To handle it properly, add an acknowledgement mechanism, so that Ctrl+v
is injected over AOA only after the SET_CLIPBOARD request has been
performed and acknowledged by the server.
Refs e4163321f00bb3830c6049bdb6c1515e7cc668a0
Refs 45b0f8123a52f5c73a5860d616f4ceba2766ca6a
PR #2814 <https://github.com/Genymobile/scrcpy/pull/2814>
2021-11-21 17:40:11 +01:00
|
|
|
ssize_t consumed = process_msgs(receiver, buf, head);
|
2019-05-30 00:25:37 +02:00
|
|
|
if (consumed == -1) {
|
|
|
|
|
// an error occurred
|
2024-07-01 08:17:33 +02:00
|
|
|
error = true;
|
2019-05-30 00:25:37 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (consumed) {
|
2020-06-04 22:44:43 +02:00
|
|
|
head -= consumed;
|
2019-05-30 00:25:37 +02:00
|
|
|
// shift the remaining data in the buffer
|
2020-06-04 22:44:43 +02:00
|
|
|
memmove(buf, &buf[consumed], head);
|
2019-05-30 00:25:37 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-01 08:17:33 +02:00
|
|
|
receiver->cbs->on_ended(receiver, error, receiver->cbs_userdata);
|
2024-05-11 16:40:22 +02:00
|
|
|
|
2019-05-30 00:25:37 +02:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
2023-02-18 09:37:13 +01:00
|
|
|
sc_receiver_start(struct sc_receiver *receiver) {
|
2019-05-30 00:25:37 +02:00
|
|
|
LOGD("Starting receiver thread");
|
|
|
|
|
|
2021-12-09 21:32:11 +01:00
|
|
|
bool ok = sc_thread_create(&receiver->thread, run_receiver,
|
|
|
|
|
"scrcpy-receiver", receiver);
|
2021-01-31 18:24:35 +01:00
|
|
|
if (!ok) {
|
2022-02-05 14:06:03 +01:00
|
|
|
LOGE("Could not start receiver thread");
|
2019-05-30 00:25:37 +02:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
2023-02-18 09:37:13 +01:00
|
|
|
sc_receiver_join(struct sc_receiver *receiver) {
|
2021-01-31 18:24:35 +01:00
|
|
|
sc_thread_join(&receiver->thread, NULL);
|
2019-05-30 00:25:37 +02:00
|
|
|
}
|