Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCampbell Barton <campbell@blender.org>2022-10-20 06:02:32 +0300
committerCampbell Barton <campbell@blender.org>2022-10-20 06:39:04 +0300
commitb0eff51fb73cfd9def696e3ede5ff23817f35784 (patch)
treeef7a61cc7c8e5f34e5aa72e8d1528c1399d4cbf8
parent1e1b9eef1b1a424a1e9c0e53cf7a19db053b1f0c (diff)
GHOST/Wayland: primary clipboard support
Match X11's primary clipboard support (typically used for MMB to paste the previous selection).
-rw-r--r--intern/ghost/CMakeLists.txt4
-rw-r--r--intern/ghost/intern/GHOST_SystemWayland.cpp341
-rw-r--r--intern/ghost/intern/GHOST_SystemWayland.h3
3 files changed, 341 insertions, 7 deletions
diff --git a/intern/ghost/CMakeLists.txt b/intern/ghost/CMakeLists.txt
index 72621648608..c05f2a327b1 100644
--- a/intern/ghost/CMakeLists.txt
+++ b/intern/ghost/CMakeLists.txt
@@ -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)
diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp
index b8b9fda8f74..6db8b9d33f8 100644
--- a/intern/ghost/intern/GHOST_SystemWayland.cpp
+++ b/intern/ghost/intern/GHOST_SystemWayland.cpp
@@ -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.
*
@@ -2881,6 +2983,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) \
diff --git a/intern/ghost/intern/GHOST_SystemWayland.h b/intern/ghost/intern/GHOST_SystemWayland.h
index ca78ccb89ab..ddcd63610ef 100644
--- a/intern/ghost/intern/GHOST_SystemWayland.h
+++ b/intern/ghost/intern/GHOST_SystemWayland.h
@@ -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_;
};