Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions app/src/control_msg.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <stdlib.h>
#include <string.h>

#include "device_msg.h"
#include "util/binary.h"
#include "util/log.h"
#include "util/str.h"
Expand Down Expand Up @@ -151,6 +152,20 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
size_t len = write_string(&buf[10], msg->set_clipboard.text,
SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH);
return 10 + len;
case SC_CONTROL_MSG_TYPE_SET_IMAGE_CLIPBOARD:
sc_write64be(&buf[1], msg->set_image_clipboard.sequence);
buf[9] = !!msg->set_image_clipboard.paste;

size_t mimetype_len = strlen(msg->set_image_clipboard.mimetype);
sc_write32be(&buf[10], mimetype_len);
memcpy(&buf[14], msg->set_image_clipboard.mimetype, mimetype_len);

// Write image data length and data
sc_write32be(&buf[14 + mimetype_len], msg->set_image_clipboard.size);
memcpy(&buf[18 + mimetype_len], msg->set_image_clipboard.data,
msg->set_image_clipboard.size);

return 18 + mimetype_len + msg->set_image_clipboard.size;
case SC_CONTROL_MSG_TYPE_SET_DISPLAY_POWER:
buf[1] = msg->set_display_power.on;
return 2;
Expand Down Expand Up @@ -269,6 +284,13 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
msg->set_clipboard.paste ? "paste" : "nopaste",
msg->set_clipboard.text);
break;
case SC_CONTROL_MSG_TYPE_SET_IMAGE_CLIPBOARD:
LOG_CMSG("image clipboard %" PRIu64_ " %s size=%u mimetype=\"%s\"",
msg->set_image_clipboard.sequence,
msg->set_image_clipboard.paste ? "paste" : "nopaste",
msg->set_image_clipboard.size,
msg->set_image_clipboard.mimetype);
break;
case SC_CONTROL_MSG_TYPE_SET_DISPLAY_POWER:
LOG_CMSG("display power %s",
msg->set_display_power.on ? "on" : "off");
Expand Down Expand Up @@ -358,6 +380,10 @@ sc_control_msg_destroy(struct sc_control_msg *msg) {
case SC_CONTROL_MSG_TYPE_SET_CLIPBOARD:
free(msg->set_clipboard.text);
break;
case SC_CONTROL_MSG_TYPE_SET_IMAGE_CLIPBOARD:
free(msg->set_image_clipboard.data);
free(msg->set_image_clipboard.mimetype);
break;
case SC_CONTROL_MSG_TYPE_START_APP:
free(msg->start_app.name);
break;
Expand Down
10 changes: 9 additions & 1 deletion app/src/control_msg.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ enum sc_control_msg_type {
SC_CONTROL_MSG_TYPE_CAMERA_SET_TORCH,
SC_CONTROL_MSG_TYPE_CAMERA_ZOOM_IN,
SC_CONTROL_MSG_TYPE_CAMERA_ZOOM_OUT,
};
SC_CONTROL_MSG_TYPE_SET_IMAGE_CLIPBOARD,
};

enum sc_copy_key {
SC_COPY_KEY_NONE,
Expand Down Expand Up @@ -117,6 +118,13 @@ struct sc_control_msg {
struct {
bool on;
} camera_set_torch;
struct {
uint64_t sequence;
uint8_t *data; // owned, to be freed by free()
uint32_t size;
char *mimetype; // owned, to be freed by free()
bool paste;
} set_image_clipboard;
};
};

Expand Down
43 changes: 43 additions & 0 deletions app/src/device_msg.c
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,45 @@ sc_device_msg_deserialize(const uint8_t *buf, size_t len,

return 5 + size;
}
case DEVICE_MSG_TYPE_IMAGE_CLIPBOARD: {
if (len < 9) {
// at least type + mimetype length (4 bytes) + data length (4 bytes)
return 0; // no complete message
}

uint32_t mimetype_len = sc_read32be(&buf[1]);
uint32_t data_len = sc_read32be(&buf[5]);

if (mimetype_len + data_len > len - 9) {
return 0; // no complete message
}

char *mimetype = malloc(mimetype_len + 1);
if (!mimetype) {
LOG_OOM();
return -1;
}
if (mimetype_len) {
memcpy(mimetype, &buf[9], mimetype_len);
}
mimetype[mimetype_len] = '\0';

uint8_t *image_data = malloc(data_len);
if (!image_data) {
LOG_OOM();
free(mimetype);
return -1;
}
if (data_len) {
memcpy(image_data, &buf[9 + mimetype_len], data_len);
}

msg->image_clipboard.mimetype = mimetype;
msg->image_clipboard.data = image_data;
msg->image_clipboard.size = data_len;

return 9 + mimetype_len + data_len;
}
default:
LOGW("Unknown device message type: %d", (int) msg->type);
return -1; // error, we cannot recover
Expand All @@ -86,6 +125,10 @@ sc_device_msg_destroy(struct sc_device_msg *msg) {
case DEVICE_MSG_TYPE_UHID_OUTPUT:
free(msg->uhid_output.data);
break;
case DEVICE_MSG_TYPE_IMAGE_CLIPBOARD:
free(msg->image_clipboard.data);
free(msg->image_clipboard.mimetype);
break;
default:
// nothing to do
break;
Expand Down
6 changes: 6 additions & 0 deletions app/src/device_msg.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ enum sc_device_msg_type {
DEVICE_MSG_TYPE_CLIPBOARD,
DEVICE_MSG_TYPE_ACK_CLIPBOARD,
DEVICE_MSG_TYPE_UHID_OUTPUT,
DEVICE_MSG_TYPE_IMAGE_CLIPBOARD,
};

struct sc_device_msg {
Expand All @@ -31,6 +32,11 @@ struct sc_device_msg {
uint16_t size;
uint8_t *data; // owned, to be freed by free()
} uhid_output;
struct {
uint8_t *data; // owned, to be freed by free()
uint32_t size;
char *mimetype; // owned, to be freed by free()
} image_clipboard;
};
};

Expand Down
63 changes: 63 additions & 0 deletions app/src/input_manager.c
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,74 @@ get_device_clipboard(struct sc_input_manager *im, enum sc_copy_key copy_key) {
return true;
}

bool
sc_input_manager_set_device_image_clipboard(struct sc_input_manager *im, bool paste,
uint64_t sequence) {
assert(im->controller && im->kp && !im->camera);

// Try common image MIME types to check if image clipboard data exists
const char* image_mime_types[] = {
"image/png",
"image/jpeg",
"image/jpg",
"image/gif",
"image/webp",
"image/bmp",
NULL
};

for (int i = 0; image_mime_types[i]; i++) {
const char* mime_type = image_mime_types[i];
size_t size;
void *img_data = SDL_GetClipboardData(mime_type, &size);
if (img_data && size > 0) {
size_t mimetype_len = strlen(mime_type);

// Check if message exceeds max size
size_t msg_size = 18 + mimetype_len + size;
if (msg_size > SC_CONTROL_MSG_MAX_SIZE) {
LOGW("Image clipboard message too large: %zu bytes, dropping",
msg_size);
return true;
}

struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_SET_IMAGE_CLIPBOARD;
msg.set_image_clipboard.sequence = sequence;
msg.set_image_clipboard.data = malloc(size);
if (msg.set_image_clipboard.data) {
memcpy(msg.set_image_clipboard.data, img_data, size);
msg.set_image_clipboard.size = size;
msg.set_image_clipboard.mimetype = strdup(mime_type);
msg.set_image_clipboard.paste = paste;

bool success = sc_controller_push_msg(im->controller, &msg);
SDL_free(img_data);
if (success) {
return true;
}
free(msg.set_image_clipboard.data);
free(msg.set_image_clipboard.mimetype);
}
SDL_free(img_data);
}
}

// Return false since we can't actually call the SDL3 functions in this context
return false;
}

static bool
set_device_clipboard(struct sc_input_manager *im, bool paste,
uint64_t sequence) {
assert(im->controller && im->kp && !im->camera);

if (sc_input_manager_set_device_image_clipboard(im, paste, sequence)) {
// Successfully sent image clipboard, return true
return true;
}

// Fallback to text clipboard
char *text = SDL_GetClipboardText();
if (!text) {
LOGW("Could not get clipboard text: %s", SDL_GetError());
Expand Down
5 changes: 5 additions & 0 deletions app/src/input_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,9 @@ void
sc_input_manager_handle_event(struct sc_input_manager *im,
const SDL_Event *event);

// Send image clipboard from computer to device
bool
sc_input_manager_set_device_image_clipboard(struct sc_input_manager *im, bool paste,
uint64_t sequence);

#endif
101 changes: 101 additions & 0 deletions app/src/receiver.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <assert.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include <SDL3/SDL_clipboard.h>

#include "device_msg.h"
Expand All @@ -18,6 +19,28 @@ struct sc_uhid_output_task_data {
uint8_t *data;
};

struct sc_image_clipboard_data {
uint8_t *data;
uint32_t size;
char *mimetype;
};

static const void * SDLCALL
clipboard_data_callback(void *userdata, const char *mime_type, size_t *size) {
struct sc_image_clipboard_data *image_data = userdata;
(void) mime_type;
*size = image_data->size;
return image_data->data;
}

static void SDLCALL
clipboard_cleanup_callback(void *userdata) {
struct sc_image_clipboard_data *image_data = userdata;
free(image_data->data);
free(image_data->mimetype);
free(image_data);
}

bool
sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket,
const struct sc_receiver_callbacks *cbs, void *cbs_userdata) {
Expand Down Expand Up @@ -65,6 +88,44 @@ task_set_clipboard(void *userdata) {
free(text);
}

static void
task_set_image_clipboard(void *userdata) {
assert(sc_thread_get_id() == SC_MAIN_THREAD_ID);

struct sc_device_msg *msg = userdata;

struct sc_image_clipboard_data *image_data =
malloc(sizeof(struct sc_image_clipboard_data));
if (!image_data) {
LOG_OOM();
free(msg->image_clipboard.data);
free(msg->image_clipboard.mimetype);
free(msg);
return;
}

image_data->data = msg->image_clipboard.data;
image_data->size = msg->image_clipboard.size;
image_data->mimetype = msg->image_clipboard.mimetype;

const char *mime_types[1] = { image_data->mimetype };

bool ok = SDL_SetClipboardData(clipboard_data_callback,
clipboard_cleanup_callback,
image_data,
mime_types, 1);
if (ok) {
LOGI("Device image clipboard copied");
} else {
LOGE("Could not set image clipboard: %s", SDL_GetError());
free(image_data->data);
free(image_data->mimetype);
free(image_data);
}

free(msg);
}

static void
task_uhid_output(void *userdata) {
assert(sc_thread_get_id() == SC_MAIN_THREAD_ID);
Expand Down Expand Up @@ -94,6 +155,46 @@ process_msg(struct sc_receiver *receiver, struct sc_device_msg *msg) {

break;
}
case DEVICE_MSG_TYPE_IMAGE_CLIPBOARD: {
// Create a copy of the message to send to the main thread
struct sc_device_msg *msg_copy = malloc(sizeof(struct sc_device_msg));
if (!msg_copy) {
LOG_OOM();
return;
}

msg_copy->type = DEVICE_MSG_TYPE_IMAGE_CLIPBOARD;
msg_copy->image_clipboard.data = malloc(msg->image_clipboard.size);
if (!msg_copy->image_clipboard.data) {
LOG_OOM();
free(msg_copy);
return;
}
memcpy(msg_copy->image_clipboard.data, msg->image_clipboard.data, msg->image_clipboard.size);
msg_copy->image_clipboard.size = msg->image_clipboard.size;

// Duplicate the mimetype string
size_t mimetype_len = strlen(msg->image_clipboard.mimetype);
msg_copy->image_clipboard.mimetype = malloc(mimetype_len + 1);
if (!msg_copy->image_clipboard.mimetype) {
LOG_OOM();
free(msg_copy->image_clipboard.data);
free(msg_copy);
return;
}
strcpy(msg_copy->image_clipboard.mimetype, msg->image_clipboard.mimetype);

bool ok = sc_post_to_main_thread(task_set_image_clipboard, msg_copy);
if (!ok) {
LOGW("Could not post image clipboard to main thread");
free(msg_copy->image_clipboard.data);
free(msg_copy->image_clipboard.mimetype);
free(msg_copy);
return;
}

break;
}
case DEVICE_MSG_TYPE_ACK_CLIPBOARD:
LOGD("Ack device clipboard sequence=%" PRIu64_,
msg->ack_clipboard.sequence);
Expand Down
Loading