GHOST/Wayland: primary clipboard support
Match X11's primary clipboard support (typically used for MMB to paste the previous selection).
This commit is contained in:
parent
1e1b9eef1b
commit
b0eff51fb7
Notes:
blender-bot
2023-02-13 22:37:43 +01:00
Referenced by issue #76428, GHOST/Wayland Support
|
@ -376,6 +376,10 @@ elseif(WITH_GHOST_X11 OR WITH_GHOST_WAYLAND)
|
|||
generate_protocol_bindings(
|
||||
"${WAYLAND_PROTOCOLS_DIR}/unstable/tablet/tablet-unstable-v2.xml"
|
||||
)
|
||||
# Primary-selection.
|
||||
generate_protocol_bindings(
|
||||
"${WAYLAND_PROTOCOLS_DIR}/unstable/primary-selection/primary-selection-unstable-v1.xml"
|
||||
)
|
||||
|
||||
add_definitions(-DWITH_GHOST_WAYLAND)
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@
|
|||
|
||||
/* Generated by `wayland-scanner`. */
|
||||
#include <pointer-constraints-unstable-v1-client-protocol.h>
|
||||
#include <primary-selection-unstable-v1-client-protocol.h>
|
||||
#include <relative-pointer-unstable-v1-client-protocol.h>
|
||||
#include <tablet-unstable-v2-client-protocol.h>
|
||||
#include <xdg-output-unstable-v1-client-protocol.h>
|
||||
|
@ -426,6 +427,53 @@ static void gwl_xdg_decor_system_destroy(GWL_XDG_Decor_System *decor)
|
|||
delete decor;
|
||||
}
|
||||
|
||||
struct GWL_PrimarySelection_DataOffer {
|
||||
struct zwp_primary_selection_offer_v1 *id = nullptr;
|
||||
std::atomic<bool> in_use = false;
|
||||
|
||||
std::unordered_set<std::string> types;
|
||||
};
|
||||
|
||||
struct GWL_PrimarySelection_DataSource {
|
||||
struct zwp_primary_selection_source_v1 *wl_source = nullptr;
|
||||
char *buffer_out = nullptr;
|
||||
size_t buffer_out_len = 0;
|
||||
};
|
||||
|
||||
/** Primary selection support. */
|
||||
struct GWL_PrimarySelection {
|
||||
|
||||
GWL_PrimarySelection_DataSource *data_source = nullptr;
|
||||
std::mutex data_source_mutex;
|
||||
|
||||
GWL_PrimarySelection_DataOffer *data_offer = nullptr;
|
||||
std::mutex data_offer_mutex;
|
||||
};
|
||||
|
||||
static void gwl_primary_selection_discard_offer(GWL_PrimarySelection *primary)
|
||||
{
|
||||
if (primary->data_offer == nullptr) {
|
||||
return;
|
||||
}
|
||||
zwp_primary_selection_offer_v1_destroy(primary->data_offer->id);
|
||||
delete primary->data_offer;
|
||||
primary->data_offer = nullptr;
|
||||
}
|
||||
|
||||
static void gwl_primary_selection_discard_source(GWL_PrimarySelection *primary)
|
||||
{
|
||||
GWL_PrimarySelection_DataSource *data_source = primary->data_source;
|
||||
if (data_source == nullptr) {
|
||||
return;
|
||||
}
|
||||
free(data_source->buffer_out);
|
||||
if (data_source->wl_source) {
|
||||
zwp_primary_selection_source_v1_destroy(data_source->wl_source);
|
||||
}
|
||||
delete primary->data_source;
|
||||
primary->data_source = nullptr;
|
||||
}
|
||||
|
||||
struct GWL_Seat {
|
||||
GHOST_SystemWayland *system = nullptr;
|
||||
|
||||
|
@ -515,6 +563,9 @@ struct GWL_Seat {
|
|||
struct GWL_DataSource *data_source = nullptr;
|
||||
std::mutex data_source_mutex;
|
||||
|
||||
struct zwp_primary_selection_device_v1 *primary_selection_device = nullptr;
|
||||
struct GWL_PrimarySelection primary_selection;
|
||||
|
||||
/** Last device that was active. */
|
||||
uint32_t data_source_serial = 0;
|
||||
};
|
||||
|
@ -540,6 +591,8 @@ struct GWL_Display {
|
|||
struct zwp_tablet_manager_v2 *tablet_manager = nullptr;
|
||||
struct zwp_relative_pointer_manager_v1 *relative_pointer_manager = nullptr;
|
||||
struct zwp_pointer_constraints_v1 *pointer_constraints = nullptr;
|
||||
|
||||
struct zwp_primary_selection_device_manager_v1 *primary_selection_device_manager = nullptr;
|
||||
};
|
||||
|
||||
#undef LOG
|
||||
|
@ -642,6 +695,22 @@ static void display_destroy(GWL_Display *display)
|
|||
}
|
||||
}
|
||||
|
||||
{
|
||||
GWL_PrimarySelection *primary = &seat->primary_selection;
|
||||
std::lock_guard lock{primary->data_offer_mutex};
|
||||
gwl_primary_selection_discard_offer(primary);
|
||||
}
|
||||
|
||||
{
|
||||
GWL_PrimarySelection *primary = &seat->primary_selection;
|
||||
std::lock_guard lock{primary->data_source_mutex};
|
||||
gwl_primary_selection_discard_source(primary);
|
||||
}
|
||||
|
||||
if (seat->primary_selection_device) {
|
||||
zwp_primary_selection_device_v1_destroy(seat->primary_selection_device);
|
||||
}
|
||||
|
||||
if (seat->data_device) {
|
||||
wl_data_device_release(seat->data_device);
|
||||
}
|
||||
|
@ -696,6 +765,10 @@ static void display_destroy(GWL_Display *display)
|
|||
zwp_pointer_constraints_v1_destroy(display->pointer_constraints);
|
||||
}
|
||||
|
||||
if (display->primary_selection_device_manager) {
|
||||
zwp_primary_selection_device_manager_v1_destroy(display->primary_selection_device_manager);
|
||||
}
|
||||
|
||||
if (display->wl_compositor) {
|
||||
wl_compositor_destroy(display->wl_compositor);
|
||||
}
|
||||
|
@ -1250,6 +1323,35 @@ static std::string read_pipe(GWL_DataOffer *data_offer,
|
|||
return data;
|
||||
}
|
||||
|
||||
static std::string read_pipe_primary(GWL_PrimarySelection_DataOffer *data_offer,
|
||||
const std::string mime_receive,
|
||||
std::mutex *mutex)
|
||||
{
|
||||
int pipefd[2];
|
||||
if (UNLIKELY(pipe(pipefd) != 0)) {
|
||||
return {};
|
||||
}
|
||||
zwp_primary_selection_offer_v1_receive(data_offer->id, mime_receive.c_str(), pipefd[1]);
|
||||
close(pipefd[1]);
|
||||
|
||||
data_offer->in_use.store(false);
|
||||
|
||||
if (mutex) {
|
||||
mutex->unlock();
|
||||
}
|
||||
/* WARNING: `data_offer_base` may be freed from now on. */
|
||||
|
||||
std::string data;
|
||||
ssize_t len;
|
||||
char buffer[4096];
|
||||
while ((len = read(pipefd[0], buffer, sizeof(buffer))) > 0) {
|
||||
data.insert(data.end(), buffer, buffer + len);
|
||||
}
|
||||
close(pipefd[0]);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* A target accepts an offered mime type.
|
||||
*
|
||||
|
@ -2880,6 +2982,158 @@ static const struct wl_keyboard_listener keyboard_listener = {
|
|||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Listener (Primary Selection Offer), #zwp_primary_selection_offer_v1_listener
|
||||
* \{ */
|
||||
|
||||
static CLG_LogRef LOG_WL_PRIMARY_SELECTION_OFFER = {"ghost.wl.handle.primary_selection_offer"};
|
||||
#define LOG (&LOG_WL_PRIMARY_SELECTION_OFFER)
|
||||
|
||||
static void primary_selection_offer_offer(void *data,
|
||||
struct zwp_primary_selection_offer_v1 *id,
|
||||
const char *type)
|
||||
{
|
||||
GWL_PrimarySelection_DataOffer *data_offer = static_cast<GWL_PrimarySelection_DataOffer *>(data);
|
||||
if (data_offer->id != id) {
|
||||
CLOG_INFO(LOG, 2, "offer: %p: offer for unknown selection %p of %s (skipped)", data, id, type);
|
||||
return;
|
||||
}
|
||||
|
||||
data_offer->types.insert(std::string(type));
|
||||
}
|
||||
|
||||
static const struct zwp_primary_selection_offer_v1_listener primary_selection_offer_listener = {
|
||||
primary_selection_offer_offer,
|
||||
};
|
||||
|
||||
#undef LOG
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Listener (Primary Selection Device), #zwp_primary_selection_device_v1_listener
|
||||
* \{ */
|
||||
|
||||
static CLG_LogRef LOG_WL_PRIMARY_SELECTION_DEVICE = {"ghost.wl.handle.primary_selection_device"};
|
||||
#define LOG (&LOG_WL_PRIMARY_SELECTION_DEVICE)
|
||||
|
||||
static void primary_selection_device_handle_data_offer(
|
||||
void * /*data*/,
|
||||
struct zwp_primary_selection_device_v1 * /*zwp_primary_selection_device_v1*/,
|
||||
struct zwp_primary_selection_offer_v1 *id)
|
||||
{
|
||||
CLOG_INFO(LOG, 2, "data_offer");
|
||||
|
||||
GWL_PrimarySelection_DataOffer *data_offer = new GWL_PrimarySelection_DataOffer;
|
||||
data_offer->id = id;
|
||||
zwp_primary_selection_offer_v1_add_listener(id, &primary_selection_offer_listener, data_offer);
|
||||
}
|
||||
|
||||
static void primary_selection_device_handle_selection(
|
||||
void *data,
|
||||
struct zwp_primary_selection_device_v1 * /*zwp_primary_selection_device_v1*/,
|
||||
struct zwp_primary_selection_offer_v1 *id)
|
||||
{
|
||||
GWL_PrimarySelection *primary = static_cast<GWL_PrimarySelection *>(data);
|
||||
|
||||
std::lock_guard lock{primary->data_offer_mutex};
|
||||
|
||||
/* Delete old data offer. */
|
||||
if (primary->data_offer != nullptr) {
|
||||
gwl_primary_selection_discard_offer(primary);
|
||||
}
|
||||
|
||||
if (id == nullptr) {
|
||||
CLOG_INFO(LOG, 2, "selection: (skipped)");
|
||||
return;
|
||||
}
|
||||
CLOG_INFO(LOG, 2, "selection");
|
||||
/* Get new data offer. */
|
||||
GWL_PrimarySelection_DataOffer *data_offer = static_cast<GWL_PrimarySelection_DataOffer *>(
|
||||
zwp_primary_selection_offer_v1_get_user_data(id));
|
||||
primary->data_offer = data_offer;
|
||||
|
||||
auto read_selection_fn = [](GWL_PrimarySelection *primary) {
|
||||
GHOST_SystemWayland *system = static_cast<GHOST_SystemWayland *>(GHOST_ISystem::getSystem());
|
||||
primary->data_offer_mutex.lock();
|
||||
|
||||
GWL_PrimarySelection_DataOffer *data_offer = primary->data_offer;
|
||||
std::string mime_receive;
|
||||
for (const std::string type : {mime_text_utf8, mime_text_plain}) {
|
||||
if (data_offer->types.count(type)) {
|
||||
mime_receive = type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const std::string data = read_pipe_primary(
|
||||
data_offer, mime_receive, &primary->data_offer_mutex);
|
||||
|
||||
{
|
||||
std::lock_guard lock{system_clipboard_mutex};
|
||||
system->clipboard_primary_set(data);
|
||||
}
|
||||
};
|
||||
|
||||
std::thread read_thread(read_selection_fn, primary);
|
||||
read_thread.detach();
|
||||
}
|
||||
|
||||
static const struct zwp_primary_selection_device_v1_listener primary_selection_device_listener = {
|
||||
primary_selection_device_handle_data_offer,
|
||||
primary_selection_device_handle_selection,
|
||||
};
|
||||
|
||||
#undef LOG
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Listener (Primary Selection Source), #zwp_primary_selection_source_v1_listener
|
||||
* \{ */
|
||||
|
||||
static CLG_LogRef LOG_WL_PRIMARY_SELECTION_SOURCE = {"ghost.wl.handle.primary_selection_source"};
|
||||
#define LOG (&LOG_WL_PRIMARY_SELECTION_SOURCE)
|
||||
|
||||
static void primary_selection_source_send(void *data,
|
||||
struct zwp_primary_selection_source_v1 * /*source*/,
|
||||
const char * /*mime_type*/,
|
||||
int32_t fd)
|
||||
{
|
||||
CLOG_INFO(LOG, 2, "send");
|
||||
|
||||
GWL_PrimarySelection *primary = static_cast<GWL_PrimarySelection *>(data);
|
||||
|
||||
std::lock_guard lock{primary->data_source_mutex};
|
||||
GWL_PrimarySelection_DataSource *data_source = primary->data_source;
|
||||
|
||||
const char *const buffer = data_source->buffer_out;
|
||||
if (write(fd, buffer, data_source->buffer_out_len) < 0) {
|
||||
GHOST_PRINT("error writing to primary clipboard: " << std::strerror(errno) << std::endl);
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void primary_selection_source_cancelled(void *data,
|
||||
struct zwp_primary_selection_source_v1 *source)
|
||||
{
|
||||
CLOG_INFO(LOG, 2, "cancelled");
|
||||
|
||||
GWL_PrimarySelection *primary = static_cast<GWL_PrimarySelection *>(data);
|
||||
|
||||
if (source == primary->data_source->wl_source) {
|
||||
gwl_primary_selection_discard_source(primary);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct zwp_primary_selection_source_v1_listener primary_selection_source_listener = {
|
||||
primary_selection_source_send,
|
||||
primary_selection_source_cancelled,
|
||||
};
|
||||
|
||||
#undef LOG
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Listener (Seat), #wl_seat_listener
|
||||
* \{ */
|
||||
|
@ -2927,6 +3181,17 @@ static void seat_handle_capabilities(void *data,
|
|||
seat->wl_keyboard = wl_seat_get_keyboard(wl_seat);
|
||||
wl_keyboard_add_listener(seat->wl_keyboard, &keyboard_listener, data);
|
||||
}
|
||||
|
||||
if (seat->system) {
|
||||
zwp_primary_selection_device_manager_v1 *primary_selection_device_manager =
|
||||
seat->system->wl_primary_selection_manager();
|
||||
seat->primary_selection_device = zwp_primary_selection_device_manager_v1_get_device(
|
||||
primary_selection_device_manager, seat->wl_seat);
|
||||
|
||||
zwp_primary_selection_device_v1_add_listener(seat->primary_selection_device,
|
||||
&primary_selection_device_listener,
|
||||
&seat->primary_selection);
|
||||
}
|
||||
}
|
||||
|
||||
static void seat_handle_name(void *data, struct wl_seat * /*wl_seat*/, const char *name)
|
||||
|
@ -3297,6 +3562,12 @@ static void global_handle_add(void *data,
|
|||
display->pointer_constraints = static_cast<zwp_pointer_constraints_v1 *>(
|
||||
wl_registry_bind(wl_registry, name, &zwp_pointer_constraints_v1_interface, 1));
|
||||
}
|
||||
else if (!strcmp(interface, zwp_primary_selection_device_manager_v1_interface.name)) {
|
||||
display->primary_selection_device_manager =
|
||||
static_cast<zwp_primary_selection_device_manager_v1 *>(wl_registry_bind(
|
||||
wl_registry, name, &zwp_primary_selection_device_manager_v1_interface, 1));
|
||||
}
|
||||
|
||||
else {
|
||||
found = false;
|
||||
|
||||
|
@ -3564,20 +3835,52 @@ GHOST_TSuccess GHOST_SystemWayland::getButtons(GHOST_Buttons &buttons) const
|
|||
return GHOST_kSuccess;
|
||||
}
|
||||
|
||||
char *GHOST_SystemWayland::getClipboard(bool /*selection*/) const
|
||||
char *GHOST_SystemWayland::getClipboard(bool selection) const
|
||||
{
|
||||
char *clipboard = static_cast<char *>(malloc(clipboard_.size() + 1));
|
||||
memcpy(clipboard, clipboard_.data(), clipboard_.size() + 1);
|
||||
const std::string &buf = selection ? clipboard_primary_ : clipboard_;
|
||||
char *clipboard = static_cast<char *>(malloc(buf.size() + 1));
|
||||
memcpy(clipboard, buf.data(), buf.size() + 1);
|
||||
return clipboard;
|
||||
}
|
||||
|
||||
void GHOST_SystemWayland::putClipboard(const char *buffer, bool /*selection*/) const
|
||||
static void system_clipboard_put_primary_selection(GWL_Display *display, const char *buffer)
|
||||
{
|
||||
if (UNLIKELY(!display_->data_device_manager || display_->seats.empty())) {
|
||||
if (!display->primary_selection_device_manager) {
|
||||
return;
|
||||
}
|
||||
GWL_Seat *seat = display->seats[0];
|
||||
GWL_PrimarySelection *primary = &seat->primary_selection;
|
||||
|
||||
GWL_Seat *seat = display_->seats[0];
|
||||
std::lock_guard lock{primary->data_source_mutex};
|
||||
|
||||
gwl_primary_selection_discard_source(primary);
|
||||
|
||||
GWL_PrimarySelection_DataSource *data_source = new GWL_PrimarySelection_DataSource;
|
||||
primary->data_source = data_source;
|
||||
|
||||
data_source->buffer_out_len = strlen(buffer);
|
||||
data_source->buffer_out = static_cast<char *>(malloc(data_source->buffer_out_len));
|
||||
std::memcpy(data_source->buffer_out, buffer, data_source->buffer_out_len);
|
||||
|
||||
data_source->wl_source = zwp_primary_selection_device_manager_v1_create_source(
|
||||
display->primary_selection_device_manager);
|
||||
|
||||
zwp_primary_selection_source_v1_add_listener(
|
||||
data_source->wl_source, &primary_selection_source_listener, primary);
|
||||
|
||||
for (const std::string &type : mime_send) {
|
||||
zwp_primary_selection_source_v1_offer(data_source->wl_source, type.c_str());
|
||||
}
|
||||
|
||||
if (seat->primary_selection_device) {
|
||||
zwp_primary_selection_device_v1_set_selection(
|
||||
seat->primary_selection_device, data_source->wl_source, seat->data_source_serial);
|
||||
}
|
||||
}
|
||||
|
||||
static void system_clipboard_put(GWL_Display *display, const char *buffer)
|
||||
{
|
||||
GWL_Seat *seat = display->seats[0];
|
||||
|
||||
std::lock_guard lock{seat->data_source_mutex};
|
||||
|
||||
|
@ -3590,7 +3893,7 @@ void GHOST_SystemWayland::putClipboard(const char *buffer, bool /*selection*/) c
|
|||
std::memcpy(data_source->buffer_out, buffer, data_source->buffer_out_len);
|
||||
|
||||
data_source->wl_data_source = wl_data_device_manager_create_data_source(
|
||||
display_->data_device_manager);
|
||||
display->data_device_manager);
|
||||
|
||||
wl_data_source_add_listener(data_source->wl_data_source, &data_source_listener, seat);
|
||||
|
||||
|
@ -3604,6 +3907,20 @@ void GHOST_SystemWayland::putClipboard(const char *buffer, bool /*selection*/) c
|
|||
}
|
||||
}
|
||||
|
||||
void GHOST_SystemWayland::putClipboard(const char *buffer, bool selection) const
|
||||
{
|
||||
if (UNLIKELY(!display_->data_device_manager || display_->seats.empty())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (selection) {
|
||||
system_clipboard_put_primary_selection(display_, buffer);
|
||||
}
|
||||
else {
|
||||
system_clipboard_put(display_, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t GHOST_SystemWayland::getNumDisplays() const
|
||||
{
|
||||
return display_ ? uint8_t(display_->outputs.size()) : 0;
|
||||
|
@ -4345,6 +4662,11 @@ wl_compositor *GHOST_SystemWayland::wl_compositor()
|
|||
return display_->wl_compositor;
|
||||
}
|
||||
|
||||
struct zwp_primary_selection_device_manager_v1 *GHOST_SystemWayland::wl_primary_selection_manager()
|
||||
{
|
||||
return display_->primary_selection_device_manager;
|
||||
}
|
||||
|
||||
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
|
||||
|
||||
libdecor *GHOST_SystemWayland::libdecor_context()
|
||||
|
@ -4412,6 +4734,11 @@ void GHOST_SystemWayland::clipboard_set(const std::string &clipboard)
|
|||
clipboard_ = clipboard;
|
||||
}
|
||||
|
||||
void GHOST_SystemWayland::clipboard_primary_set(const std::string &clipboard)
|
||||
{
|
||||
clipboard_primary_ = clipboard;
|
||||
}
|
||||
|
||||
void GHOST_SystemWayland::window_surface_unref(const wl_surface *wl_surface)
|
||||
{
|
||||
#define SURFACE_CLEAR_PTR(surface_test) \
|
||||
|
|
|
@ -158,6 +158,7 @@ class GHOST_SystemWayland : public GHOST_System {
|
|||
|
||||
struct wl_display *wl_display();
|
||||
struct wl_compositor *wl_compositor();
|
||||
struct zwp_primary_selection_device_manager_v1 *wl_primary_selection_manager();
|
||||
|
||||
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
|
||||
libdecor *libdecor_context();
|
||||
|
@ -173,6 +174,7 @@ class GHOST_SystemWayland : public GHOST_System {
|
|||
/* WAYLAND utility functions. */
|
||||
|
||||
void clipboard_set(const std::string &clipboard);
|
||||
void clipboard_primary_set(const std::string &clipboard);
|
||||
|
||||
/** Clear all references to this surface to prevent accessing NULL pointers. */
|
||||
void window_surface_unref(const wl_surface *wl_surface);
|
||||
|
@ -192,4 +194,5 @@ class GHOST_SystemWayland : public GHOST_System {
|
|||
private:
|
||||
struct GWL_Display *display_;
|
||||
std::string clipboard_;
|
||||
std::string clipboard_primary_;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue