diff options
author | Campbell Barton <campbell@blender.org> | 2022-10-20 06:02:32 +0300 |
---|---|---|
committer | Campbell Barton <campbell@blender.org> | 2022-10-20 06:39:04 +0300 |
commit | b0eff51fb73cfd9def696e3ede5ff23817f35784 (patch) | |
tree | ef7a61cc7c8e5f34e5aa72e8d1528c1399d4cbf8 | |
parent | 1e1b9eef1b1a424a1e9c0e53cf7a19db053b1f0c (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.txt | 4 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_SystemWayland.cpp | 341 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_SystemWayland.h | 3 |
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_; }; |