[client] clipboard/wayland: implement VM-to-host text copy

Co-authored-by: Quantum <quantum2048@gmail.com>
This commit is contained in:
Tudor Brindus 2021-01-09 00:56:29 -05:00 committed by Geoffrey McRae
parent a205515d91
commit c58f33f5ab
2 changed files with 228 additions and 0 deletions

View file

@ -20,8 +20,28 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "interface/clipboard.h" #include "interface/clipboard.h"
#include "common/debug.h" #include "common/debug.h"
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <wayland-client.h>
struct transfer {
void * data;
size_t size;
};
struct state struct state
{ {
struct wl_display * display;
struct wl_registry * registry;
struct wl_data_device_manager * data_device_manager;
struct wl_seat * seat;
struct wl_data_device * data_device;
struct wl_keyboard * keyboard;
uint32_t keyboard_enter_serial;
uint32_t capabilities;
LG_ClipboardReleaseFn releaseFn; LG_ClipboardReleaseFn releaseFn;
LG_ClipboardRequestFn requestFn; LG_ClipboardRequestFn requestFn;
LG_ClipboardNotifyFn notifyFn; LG_ClipboardNotifyFn notifyFn;
@ -31,11 +51,110 @@ struct state
static struct state * this = NULL; static struct state * this = NULL;
static const char * text_mimetypes[] =
{
"text/plain",
"text/plain;charset=utf-8",
"TEXT",
"STRING",
"UTF8_STRING",
};
static const char * wayland_cb_getName() static const char * wayland_cb_getName()
{ {
return "Wayland"; return "Wayland";
} }
// Keyboard-handling listeners.
static void keyboard_keymap_handler(
void * data,
struct wl_keyboard * keyboard,
uint32_t format,
int fd,
uint32_t size
) {
close(fd);
}
static void keyboard_enter_handler(void * data, struct wl_keyboard * keyboard,
uint32_t serial, struct wl_surface * surface, struct wl_array * keys)
{
this->keyboard_enter_serial = serial;
}
static void keyboard_leave_handler(void * data, struct wl_keyboard * keyboard,
uint32_t serial, struct wl_surface * surface)
{
// Do nothing.
}
static void keyboard_key_handler(void * data, struct wl_keyboard * keyboard,
uint32_t serial, uint32_t time, uint32_t key, uint32_t state)
{
// Do nothing.
}
static void keyboard_modifiers_handler(void * data,
struct wl_keyboard * keyboard, uint32_t serial, uint32_t mods_depressed,
uint32_t mods_latched, uint32_t mods_locked, uint32_t group)
{
// Do nothing.
}
static const struct wl_keyboard_listener keyboard_listener = {
.keymap = keyboard_keymap_handler,
.enter = keyboard_enter_handler,
.leave = keyboard_leave_handler,
.key = keyboard_key_handler,
.modifiers = keyboard_modifiers_handler,
};
// Seat-handling listeners.
static void seat_capabilities_handler(void * data, struct wl_seat * seat,
uint32_t capabilities)
{
this->capabilities = capabilities;
}
static void seat_name_handler(void * data, struct wl_seat * seat,
const char * name)
{
// Do nothing.
}
static const struct wl_seat_listener seat_listener = {
.capabilities = seat_capabilities_handler,
.name = seat_name_handler
};
// Registry-handling listeners.
static void registry_global_handler(void * data,
struct wl_registry * wl_registry, uint32_t name, const char * interface,
uint32_t version) {
if (!strcmp(interface, wl_data_device_manager_interface.name))
this->data_device_manager = wl_registry_bind(this->registry, name,
&wl_data_device_manager_interface, 3);
else if (!strcmp(interface, wl_seat_interface.name) && !this->seat)
// TODO: multi-seat support.
this->seat = wl_registry_bind(this->registry, name,
&wl_seat_interface, 1);
}
static void registry_global_remove_handler(void * data,
struct wl_registry * wl_registry, uint32_t name) {
// Do nothing.
}
static const struct wl_registry_listener registry_listener = {
.global = registry_global_handler,
.global_remove = registry_global_remove_handler,
};
// End of Wayland handlers.
static bool wayland_cb_init( static bool wayland_cb_init(
SDL_SysWMinfo * wminfo, SDL_SysWMinfo * wminfo,
LG_ClipboardReleaseFn releaseFn, LG_ClipboardReleaseFn releaseFn,
@ -54,6 +173,28 @@ static bool wayland_cb_init(
this->releaseFn = releaseFn; this->releaseFn = releaseFn;
this->notifyFn = notifyFn; this->notifyFn = notifyFn;
this->dataFn = dataFn; this->dataFn = dataFn;
this->display = wminfo->info.wl.display;
this->registry = wl_display_get_registry(this->display);
// Wait for the initial set of globals to appear.
wl_registry_add_listener(this->registry, &registry_listener, NULL);
wl_display_roundtrip(this->display);
this->data_device = wl_data_device_manager_get_data_device(
this->data_device_manager, this->seat);
// Wait for the compositor to let us know of capabilities.
wl_seat_add_listener(this->seat, &seat_listener, NULL);
wl_display_roundtrip(this->display);
if (!(this->capabilities & WL_SEAT_CAPABILITY_KEYBOARD))
{
// TODO: keyboard hotplug support.
DEBUG_ERROR("no keyboard");
return false;
}
this->keyboard = wl_seat_get_keyboard(this->seat);
wl_keyboard_add_listener(this->keyboard, &keyboard_listener, NULL);
return true; return true;
} }
@ -67,8 +208,91 @@ static void wayland_cb_wmevent(SDL_SysWMmsg * msg)
{ {
} }
static bool is_text_mimetype(const char * mime_type)
{
for (int i = 0; i < sizeof(text_mimetypes)/sizeof(char *); i++)
if (!strcmp(mime_type, text_mimetypes[i]))
return true;
return false;
}
static void data_source_handle_send(void * data, struct wl_data_source * source,
const char * mime_type, int fd) {
struct transfer * transfer = (struct transfer *) data;
if (is_text_mimetype(mime_type))
{
// Consider making this do non-blocking sends to not stall the Wayland
// event loop if it becomes a problem. This is "fine" in the sense that
// wl-copy also stalls like this, but it's not necessary.
fcntl(fd, F_SETFL, 0);
size_t pos = 0;
while (pos < transfer->size)
{
ssize_t written = write(fd, transfer->data + pos, transfer->size - pos);
if (written < 0)
{
DEBUG_ERROR("Failed to write clipboard data: %s", strerror(errno));
goto error;
}
pos += written;
}
}
error:
close(fd);
}
static void data_source_handle_cancelled(void * data,
struct wl_data_source * source) {
struct transfer * transfer = (struct transfer *) data;
free(transfer->data);
free(transfer);
wl_data_source_destroy(source);
}
static const struct wl_data_source_listener data_source_listener = {
.send = data_source_handle_send,
.cancelled = data_source_handle_cancelled,
};
static void wayland_cb_reply_fn(void * opaque, LG_ClipboardData type, uint8_t * data, uint32_t size)
{
struct transfer * transfer = malloc(sizeof(struct transfer));
void * data_copy = malloc(size);
memcpy(data_copy, data, size);
transfer->data = data_copy;
transfer->size = size;
struct wl_data_source *source =
wl_data_device_manager_create_data_source(this->data_device_manager);
wl_data_source_add_listener(source, &data_source_listener, transfer);
for (int i = 0; i < sizeof(text_mimetypes)/sizeof(*text_mimetypes); i++)
{
wl_data_source_offer(source, text_mimetypes[i]);
}
wl_data_device_set_selection(this->data_device, source,
this->keyboard_enter_serial);
}
static void wayland_cb_notice(LG_ClipboardRequestFn requestFn, LG_ClipboardData type) static void wayland_cb_notice(LG_ClipboardRequestFn requestFn, LG_ClipboardData type)
{ {
if (type != LG_CLIPBOARD_DATA_TEXT)
{
DEBUG_ERROR("Only text selection currently supported");
return;
}
this->requestFn = requestFn;
this->type = type;
if (!this->requestFn)
return;
this->requestFn(wayland_cb_reply_fn, NULL);
} }
static void wayland_cb_release() static void wayland_cb_release()

View file

@ -2022,6 +2022,10 @@ static int lg_run()
g_state.lgc = LG_Clipboards[0]; g_state.lgc = LG_Clipboards[0];
} }
else if (g_state.wminfo.subsystem == SDL_SYSWM_WAYLAND)
{
g_state.lgc = LG_Clipboards[1];
}
} else { } else {
DEBUG_ERROR("Could not get SDL window information %s", SDL_GetError()); DEBUG_ERROR("Could not get SDL window information %s", SDL_GetError());
return -1; return -1;