[vte] widget: Implement clipboard for gtk4



commit b72b397be45045bc5464ba8ed889f6fb2b39989a
Author: Christian Persch <chpe src gnome org>
Date:   Thu Sep 1 18:34:03 2022 +0200

    widget: Implement clipboard for gtk4
    
    Fixes: https://gitlab.gnome.org/GNOME/vte/-/issues/2557

 src/clipboard-gtk.cc | 536 ++++++++++++++++++++++++++++++++++++++++++++++++---
 src/clipboard-gtk.hh |  15 +-
 src/glib-glue.hh     |   1 +
 src/gtk-glue.hh      |   1 +
 src/vte.cc           |   4 +-
 src/widget.cc        |   5 +-
 src/widget.hh        |   3 +-
 7 files changed, 530 insertions(+), 35 deletions(-)
---
diff --git a/src/clipboard-gtk.cc b/src/clipboard-gtk.cc
index 402e3a41..17597485 100644
--- a/src/clipboard-gtk.cc
+++ b/src/clipboard-gtk.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2020 Christian Persch
+ * Copyright © 2020, 2022 Christian Persch
  *
  * This library is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Lesser General Public License as published
@@ -18,6 +18,7 @@
 #include "config.h"
 
 #include "clipboard-gtk.hh"
+#include "glib-glue.hh"
 #include "gtk-glue.hh"
 #include "widget.hh"
 #include "vteinternal.hh"
@@ -26,6 +27,9 @@
 #include <stdexcept>
 #include <utility>
 
+#define MIME_TYPE_TEXT_PLAIN_UTF8 "text/plain;charset=utf-8"
+#define MIME_TYPE_TEXT_HTML_UTF16 "text/html"
+
 namespace vte::platform {
 
 // Note:
@@ -64,9 +68,10 @@ Clipboard::Clipboard(Widget& delegate,
                 throw std::runtime_error{"Failed to create clipboard"};
 }
 
-#if VTE_GTK == 3
-
 class Clipboard::Offer {
+#if VTE_GTK == 4
+        friend class ContentProvider;
+#endif
 public:
         Offer(Clipboard& clipboard,
               OfferGetCallback get_callback,
@@ -82,30 +87,15 @@ public:
         auto& clipboard() const noexcept { return *m_clipboard; }
 
         static void run(std::unique_ptr<Offer> offer,
-                        ClipboardFormat format) noexcept
-        {
-                auto [targets, n_targets] = targets_for_format(format);
-
-                // Transfers ownership of *offer to the clipboard. If setting succeeds,
-                // the clipboard will own *offer until the clipboard_data_clear_cb
-                // callback is called.
-                // If setting the clipboard fails, the clear callback will never be
-                // called.
-                if (gtk_clipboard_set_with_data(offer->clipboard().platform(),
-                                                targets, n_targets,
-                                                clipboard_get_cb,
-                                                clipboard_clear_cb,
-                                                offer.get())) {
-                        gtk_clipboard_set_can_store(offer->clipboard().platform(), targets, n_targets);
-                        offer.release(); // transferred to clipboard above
-                }
-        }
+                        ClipboardFormat format) noexcept;
 
 private:
         std::shared_ptr<Clipboard> m_clipboard;
         OfferGetCallback m_get_callback;
         OfferClearCallback m_clear_callback;
 
+#if VTE_GTK == 3
+
         void dispatch_get(ClipboardFormat format,
                           GtkSelectionData* data) noexcept
         try
@@ -219,6 +209,7 @@ private:
                 }
         }
 
+#endif /* VTE_GTK == 3 */
 
         static std::pair<vte::glib::StringPtr, size_t>
         text_to_utf16_mozilla(std::string_view const& str) noexcept
@@ -237,7 +228,455 @@ private:
 
 }; // class Clipboard::Offer
 
-#endif /* VTE_GTK == 3 */
+
+#if VTE_GTK == 4
+
+static void* task_tag;
+
+using VteContentProvider = GdkContentProvider;
+
+class ContentProvider {
+public:
+        ContentProvider(VteContentProvider* native)
+                : m_native{native}
+        {
+        }
+
+        ContentProvider() = default;
+        ~ContentProvider() = default;
+
+        ContentProvider(ContentProvider const&) = delete;
+        ContentProvider(ContentProvider&&) = delete;
+
+        ContentProvider& operator=(ContentProvider const&) = delete;
+        ContentProvider& operator=(ContentProvider&&) = delete;
+
+        void take_offer(std::unique_ptr<Clipboard::Offer> offer)
+        {
+                m_offer = std::move(offer);
+        }
+
+        void set_format(ClipboardFormat format)
+        {
+                m_format = format;
+                m_content_formats = format_to_content_formats(format);
+        }
+
+        void content_changed()
+        {
+        }
+
+        void attach_clipboard(GdkClipboard* gdk_clipboard)
+        {
+        }
+
+        void
+        detach_clipboard(GdkClipboard* gdk_clipboard)
+        {
+                if (auto const delegate = m_offer->clipboard().m_delegate.lock()) {
+                        (*delegate.*m_offer->m_clear_callback)(m_offer->clipboard());
+                }
+        }
+
+        GdkContentFormats*
+        ref_formats()
+        {
+                return m_content_formats ? gdk_content_formats_ref(m_content_formats.get()) : nullptr;
+        }
+
+        GdkContentFormats*
+        ref_storable_formats()
+        {
+                return format_to_content_formats(ClipboardFormat::TEXT).release();
+        }
+
+        void
+        write_mime_type_async(char const* mime_type,
+                              GOutputStream* stream,
+                              int io_priority,
+                              GCancellable* cancellable,
+                              GAsyncReadyCallback callback,
+                              void* user_data)
+        {
+                auto task = vte::glib::take_ref(g_task_new(m_native, cancellable, callback, user_data));
+                g_task_set_priority(task.get(), io_priority);
+                g_task_set_source_tag(task.get(), &task_tag);
+                g_task_set_name(task.get(), "vte-content-provider-write-async");
+
+                auto const format = format_from_mime_type(mime_type);
+                if (format == ClipboardFormat::INVALID)
+                        return g_task_return_new_error(task.get(), G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+                                                       "Unknown format");
+
+                if (auto const delegate = m_offer->clipboard().m_delegate.lock()) {
+                        auto str = (*delegate.*m_offer->m_get_callback)(m_offer->clipboard(), format);
+                        if (!str)
+                                return g_task_return_new_error(task.get(), G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+                                                               "Nothing on offer");
+
+                        auto bytes = vte::Freeable<GBytes>{};
+                        switch (format_from_mime_type(mime_type)) {
+                        case ClipboardFormat::TEXT: {
+                                bytes = vte::take_freeable(g_bytes_new_with_free_func(g_strndup(str->data(), 
str->size()),
+                                                                                      str->size(),
+                                                                                      g_free, nullptr));
+                                break;
+                        }
+
+                        case ClipboardFormat::HTML: {
+                                auto [html, len] = m_offer->text_to_utf16_mozilla(*str);
+
+                                // This makes yet another copy of the data... :(
+                                if (html) {
+                                        bytes = 
vte::take_freeable(g_bytes_new_with_free_func(html.release(), len, g_free, nullptr));
+                                        break;
+                                } else {
+                                        return g_task_return_new_error(task.get(), G_IO_ERROR, 
G_IO_ERROR_INVALID_DATA,
+                                                                       "Invalid data");
+                                }
+
+                                break;
+                        }
+                        case ClipboardFormat::INVALID:
+                        default:
+                                break;
+                        }
+
+                        if (bytes) {
+                                auto provider = 
vte::glib::take_ref(gdk_content_provider_new_for_bytes(mime_type, bytes.release()));
+                                return gdk_content_provider_write_mime_type_async(provider.get(),
+                                                                                  mime_type,
+                                                                                  stream,
+                                                                                  io_priority,
+                                                                                  cancellable,
+                                                                                  
write_mime_type_async_done_cb,
+                                                                                  task.release()); // 
transfer
+                        }
+                }
+
+                return g_task_return_new_error(task.get(), G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+                                               "Offer expired");
+        }
+
+        bool
+        write_mime_type_finish(GAsyncResult* result,
+                               GError** error)
+        {
+                auto const task = G_TASK(result);
+                return g_task_propagate_boolean(task, error);
+        }
+
+        bool
+        get_value(GValue* value,
+                  GError** error)
+        {
+                if (g_value_get_gtype(value) == G_TYPE_STRING) {
+                        if (auto const delegate = m_offer->clipboard().m_delegate.lock()) {
+                                auto const str = (*delegate.*m_offer->m_get_callback)(m_offer->clipboard(), 
ClipboardFormat::TEXT);
+                                if (!str)
+                                        return false;
+
+                                g_value_take_string(value, g_strndup(str->data(), str->size()));
+                                return true;
+                        }
+                }
+
+                return false;
+        }
+
+private:
+        VteContentProvider* m_native{nullptr}; /* unowned */
+
+        std::unique_ptr<Clipboard::Offer> m_offer;
+
+        ClipboardFormat m_format{ClipboardFormat::INVALID};
+        vte::Freeable<GdkContentFormats> m_content_formats;
+
+        vte::Freeable<GdkContentFormats>
+        format_to_content_formats(ClipboardFormat format) noexcept
+        {
+                auto builder = vte::take_freeable(gdk_content_formats_builder_new());
+
+                switch (format) {
+                case ClipboardFormat::TEXT:
+                        gdk_content_formats_builder_add_mime_type(builder.get(),
+                                                                  MIME_TYPE_TEXT_PLAIN_UTF8);
+                        break;
+                case ClipboardFormat::HTML:
+                        gdk_content_formats_builder_add_mime_type(builder.get(),
+                                                                  MIME_TYPE_TEXT_HTML_UTF16);
+                        break;
+                case ClipboardFormat::INVALID:
+                default:
+                        __builtin_unreachable();
+                }
+
+                return vte::take_freeable(gdk_content_formats_builder_to_formats(builder.release()));
+        }
+
+        ClipboardFormat
+        format_from_mime_type(std::string_view const& mime_type) noexcept
+        {
+                if (mime_type == MIME_TYPE_TEXT_PLAIN_UTF8)
+                        return ClipboardFormat::TEXT;
+                else if (mime_type == MIME_TYPE_TEXT_HTML_UTF16)
+                        return ClipboardFormat::HTML;
+                else
+                        return ClipboardFormat::INVALID;
+        }
+
+        static void
+        write_mime_type_async_done_cb(GObject* source,
+                                      GAsyncResult* result,
+                                      void* user_data) noexcept
+        try
+        {
+                auto const provider = GDK_CONTENT_PROVIDER(source);
+                auto const task = vte::glib::take_ref(reinterpret_cast<GTask*>(user_data)); // ref added on 
::write_mime_type_async
+
+                auto error = vte::glib::Error{};
+                if (!gdk_content_provider_write_mime_type_finish(provider, result, error)) {
+                        return g_task_return_error(task.get(), error.release());
+                }
+
+                return g_task_return_boolean(task.get(), true);
+        }
+        catch (...)
+        {
+                vte::log_exception();
+        }
+
+}; // class ContentProvider
+
+#define VTE_TYPE_CONTENT_PROVIDER            (vte_content_provider_get_type())
+#define VTE_CONTENT_PROVIDER(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), VTE_TYPE_CONTENT_PROVIDER, 
VteContentProvider))
+#define VTE_CONTENT_PROVIDER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass),  VTE_TYPE_CONTENT_PROVIDER, 
VteContentProviderClass))
+#define VTE_IS_CONTENT_PROVIDER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), VTE_TYPE_CONTENT_PROVIDER))
+#define VTE_IS_CONTENT_PROVIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),  VTE_TYPE_CONTENT_PROVIDER))
+#define VTE_CONTENT_PROVIDER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj),  VTE_TYPE_CONTENT_PROVIDER, 
VteContentProviderClass))
+
+using VteContentProviderClass = GdkContentProviderClass;
+
+static GType vte_content_provider_get_type(void);
+
+G_DEFINE_TYPE_WITH_CODE(VteContentProvider, vte_content_provider, G_TYPE_OBJECT,
+                        {
+                                VteContentProvider_private_offset =
+                                        g_type_add_instance_private(g_define_type_id, 
sizeof(ContentProvider));
+                        });
+
+template<typename T>
+static inline auto
+IMPL(T* that)
+{
+        auto const pthat = reinterpret_cast<VteContentProvider*>(that);
+        return 
std::launder(reinterpret_cast<ContentProvider*>(vte_content_provider_get_instance_private(pthat)));
+}
+
+static void
+vte_content_provider_content_changed(GdkContentProvider* provider) noexcept
+try
+{
+        GDK_CONTENT_PROVIDER_CLASS(vte_content_provider_parent_class)->content_changed(provider);
+
+        IMPL(provider)->content_changed();
+}
+catch (...)
+{
+        vte::log_exception();
+}
+
+static void
+vte_content_provider_attach_clipboard(GdkContentProvider* provider,
+                                      GdkClipboard* clipboard) noexcept
+try
+{
+        GDK_CONTENT_PROVIDER_CLASS(vte_content_provider_parent_class)->attach_clipboard(provider,
+                                                                                        clipboard);
+
+        IMPL(provider)->attach_clipboard(clipboard);
+}
+catch (...)
+{
+        vte::log_exception();
+}
+
+static void
+vte_content_provider_detach_clipboard(GdkContentProvider* provider,
+                                      GdkClipboard* clipboard) noexcept
+try
+{
+        GDK_CONTENT_PROVIDER_CLASS(vte_content_provider_parent_class)->detach_clipboard(provider,
+                                                                                        clipboard);
+
+        IMPL(provider)->detach_clipboard(clipboard);
+}
+catch (...)
+{
+        vte::log_exception();
+}
+
+static GdkContentFormats*
+vte_content_provider_ref_formats(GdkContentProvider* provider) noexcept
+try
+{
+        return IMPL(provider)->ref_formats();
+}
+catch (...)
+{
+        vte::log_exception();
+        return nullptr;
+}
+
+static GdkContentFormats*
+vte_content_provider_ref_storable_formats(GdkContentProvider* provider) noexcept
+try
+{
+        return IMPL(provider)->ref_storable_formats();
+}
+catch (...)
+{
+        vte::log_exception();
+        return nullptr;
+}
+
+static void
+vte_content_provider_write_mime_type_async(GdkContentProvider* provider,
+                                           char const* mime_type,
+                                           GOutputStream* stream,
+                                           int io_priority,
+                                           GCancellable* cancellable,
+                                           GAsyncReadyCallback callback,
+                                           void* user_data) noexcept
+try
+{
+        return IMPL(provider)->write_mime_type_async(mime_type,
+                                                     stream,
+                                                     io_priority,
+                                                     cancellable,
+                                                     callback, user_data);
+}
+catch (...)
+{
+        vte::log_exception();
+}
+
+static gboolean
+vte_content_provider_write_mime_type_finish(GdkContentProvider* provider,
+                                            GAsyncResult* result,
+                                            GError** error) noexcept
+try
+{
+        assert(g_task_is_valid(result, provider));
+        assert(g_task_get_source_tag(G_TASK(result)) == &task_tag);
+
+        return IMPL(provider)->write_mime_type_finish(result, error);
+}
+catch (...)
+{
+        vte::glib::set_error_from_exception(error);
+        return false;
+}
+
+static gboolean
+vte_content_provider_get_value(GdkContentProvider* provider,
+                               GValue* value,
+                               GError** error) noexcept
+try
+{
+        if (IMPL(provider)->get_value(value, error))
+                return true;
+
+        return GDK_CONTENT_PROVIDER_CLASS(vte_content_provider_parent_class)->get_value(provider,
+                                                                                        value,
+                                                                                        error);
+}
+catch (...)
+{
+        vte::glib::set_error_from_exception(error);
+        return false;
+}
+
+static void
+vte_content_provider_init(VteContentProvider* provider)
+try
+{
+        auto place = vte_content_provider_get_instance_private(provider);
+        new (place) ContentProvider{provider};
+}
+catch (...)
+{
+        vte::log_exception();
+        g_error("Failed to create ContentProvider\n");
+}
+
+static void
+vte_content_provider_finalize(GObject* object) noexcept
+{
+        IMPL(object)->~ContentProvider();
+
+        G_OBJECT_CLASS(vte_content_provider_parent_class)->finalize(object);
+}
+
+static void
+vte_content_provider_class_init(VteContentProviderClass *klass)
+{
+        auto gobject_class = G_OBJECT_CLASS(klass);
+        gobject_class->finalize = vte_content_provider_finalize;
+
+        auto provider_class = GDK_CONTENT_PROVIDER_CLASS(klass);
+        provider_class->content_changed = vte_content_provider_content_changed;
+        provider_class->attach_clipboard = vte_content_provider_attach_clipboard;
+        provider_class->detach_clipboard = vte_content_provider_detach_clipboard;
+        provider_class->ref_formats = vte_content_provider_ref_formats;
+        provider_class->ref_storable_formats = vte_content_provider_ref_storable_formats;
+        provider_class->write_mime_type_async = vte_content_provider_write_mime_type_async;
+        provider_class->write_mime_type_finish = vte_content_provider_write_mime_type_finish;
+        provider_class->get_value = vte_content_provider_get_value;
+}
+
+static auto
+vte_content_provider_new(void) noexcept
+{
+        return reinterpret_cast<VteContentProvider*>
+                (g_object_new(VTE_TYPE_CONTENT_PROVIDER, nullptr));
+}
+
+#endif /* VTE_GTK == 4 */
+
+void
+Clipboard::Offer::run(std::unique_ptr<Offer> offer,
+                      ClipboardFormat format) noexcept
+{
+#if VTE_GTK == 3
+        auto [targets, n_targets] = targets_for_format(format);
+
+        // Transfers ownership of *offer to the clipboard. If setting succeeds,
+        // the clipboard will own *offer until the clipboard_data_clear_cb
+        // callback is called.
+        // If setting the clipboard fails, the clear callback will never be
+        // called.
+        if (gtk_clipboard_set_with_data(offer->clipboard().platform(),
+                                        targets, n_targets,
+                                        clipboard_get_cb,
+                                        clipboard_clear_cb,
+                                        offer.get())) {
+                gtk_clipboard_set_can_store(offer->clipboard().platform(), targets, n_targets);
+                offer.release(); // transferred to clipboard above
+        }
+#elif VTE_GTK == 4
+        // It seems that to make the content available lazily (i.e. only
+        // generate it when the clipboard contents are requested), or
+        // receive a notification when said content no longer owns the
+        // clipboard, one has to write a new GdkContentProvider implementation.
+        auto const provider = vte::glib::take_ref(vte_content_provider_new());
+        // Transfers ownership of *offer to the provider.
+        auto const impl = IMPL(provider.get());
+        impl->take_offer(std::move(offer));
+        impl->set_format(format);
+        gdk_clipboard_set_content(offer->clipboard().platform(), provider.get());
+#endif /* VTE_GTK */
+}
 
 class Clipboard::Request {
 public:
@@ -256,11 +695,16 @@ public:
 
         static void run(std::unique_ptr<Request> request) noexcept
         {
+                auto const platform = request->clipboard().platform();
 #if VTE_GTK == 3
-                auto platform = request->clipboard().platform();
                 gtk_clipboard_request_text(platform,
                                            text_received_cb,
                                            request.release());
+#elif VTE_GTK == 4
+                gdk_clipboard_read_text_async(platform,
+                                              nullptr, // cancellable
+                                              GAsyncReadyCallback(text_received_cb),
+                                              request.release());
 #endif /* VTE_GTK */
         }
 
@@ -273,7 +717,7 @@ private:
         void dispatch(char const *text) noexcept
         try
         {
-                if (auto delegate = clipboard().m_delegate.lock()) {
+                if (auto const delegate = clipboard().m_delegate.lock()) {
                         if (text)
                                 (*delegate.*m_done_callback)(clipboard(), {text, strlen(text)});
                         else
@@ -289,10 +733,42 @@ private:
                                      char const* text,
                                      gpointer data) noexcept
         {
-                auto request = std::unique_ptr<Request>{reinterpret_cast<Request*>(data)};
+                auto const request = std::unique_ptr<Request>{reinterpret_cast<Request*>(data)};
                 request->dispatch(text);
         }
 
+#elif VTE_GTK == 4
+
+        void dispatch(GObject* source,
+                      GAsyncResult* result) noexcept
+        try
+        {
+                // Well done gtk4 to not simply tell us also the length of the received text!
+                auto const text = vte::glib::take_string
+                        (gdk_clipboard_read_text_finish(GDK_CLIPBOARD(source),
+                                                        result,
+                                                        nullptr));
+
+                if (auto const delegate = clipboard().m_delegate.lock()) {
+                        if (text)
+                                (*delegate.*m_done_callback)(clipboard(), {text.get(), strlen(text.get())});
+                        else
+                                (*delegate.*m_failed_callback)(clipboard());
+                }
+        }
+        catch (...)
+        {
+                vte::log_exception();
+        }
+
+        static void text_received_cb(GObject* source,
+                                     GAsyncResult* result,
+                                     gpointer data) noexcept
+        {
+                auto const request = std::unique_ptr<Request>{reinterpret_cast<Request*>(data)};
+                request->dispatch(source, result);
+        }
+
 #endif /* VTE_GTK */
 
 }; // class Clipboard::Request
@@ -308,11 +784,15 @@ Clipboard::offer_data(ClipboardFormat format,
 }
 
 void
-Clipboard::set_text(std::string_view const& text) noexcept
+Clipboard::set_text(char const* text,
+                    size_t size) noexcept
 {
 #if VTE_GTK == 3
-        gtk_clipboard_set_text(platform(), text.data(), text.size());
-#endif
+        gtk_clipboard_set_text(platform(), text, size);
+#elif VTE_GTK == 4
+        // This API sucks
+        gdk_clipboard_set_text(platform(), text);
+#endif /* VTE_GTK */
 }
 
 void
diff --git a/src/clipboard-gtk.hh b/src/clipboard-gtk.hh
index 45660c3e..c09fd6fe 100644
--- a/src/clipboard-gtk.hh
+++ b/src/clipboard-gtk.hh
@@ -31,7 +31,10 @@ namespace vte::platform {
 
 enum class ClipboardFormat {
         TEXT,
-        HTML
+        HTML,
+#if VTE_GTK == 4
+        INVALID = -1
+#endif
 };
 
 enum class ClipboardType {
@@ -39,7 +42,14 @@ enum class ClipboardType {
         PRIMARY   = 1
 };
 
+#if VTE_GTK == 4
+class ContentProvider;
+#endif
+
 class Clipboard : public std::enable_shared_from_this<Clipboard> {
+#if VTE_GTK == 4
+        friend class ContentProvider;
+#endif
 public:
         Clipboard(Widget& delegate,
                   ClipboardType type) /* throws */;
@@ -69,7 +79,8 @@ public:
                         OfferGetCallback get_callback,
                         OfferClearCallback clear_callback) /* throws */;
 
-        void set_text(std::string_view const& text) noexcept;
+        void set_text(char const* text,
+                      size_t size) noexcept;
 
         void request_text(RequestDoneCallback done_callback,
                           RequestFailedCallback failed_callback) /* throws */;
diff --git a/src/glib-glue.hh b/src/glib-glue.hh
index 5ddd94a8..c7839cfc 100644
--- a/src/glib-glue.hh
+++ b/src/glib-glue.hh
@@ -278,6 +278,7 @@ bool set_error_from_exception(GError** error
 
 namespace vte {
 
+VTE_DECLARE_FREEABLE(GBytes, g_bytes_unref);
 VTE_DECLARE_FREEABLE(GOptionContext, g_option_context_free);
 VTE_DECLARE_FREEABLE(GVariant, g_variant_unref);
 
diff --git a/src/gtk-glue.hh b/src/gtk-glue.hh
index 71ffc9fd..443e41a6 100644
--- a/src/gtk-glue.hh
+++ b/src/gtk-glue.hh
@@ -31,6 +31,7 @@ VTE_DECLARE_FREEABLE(GtkTargetList, gtk_target_list_unref);
 
 #if VTE_GTK == 4
 VTE_DECLARE_FREEABLE(GdkContentFormats, gdk_content_formats_unref);
+VTE_DECLARE_FREEABLE(GdkContentFormatsBuilder, gdk_content_formats_builder_unref);
 #endif /* VTE_GTK == 4 */
 
 } // namespace vte
diff --git a/src/vte.cc b/src/vte.cc
index 16121f15..abac3813 100644
--- a/src/vte.cc
+++ b/src/vte.cc
@@ -7944,8 +7944,8 @@ Terminal::widget_unrealize()
                                 // FIXMEchpe we should check m_selection_format[sel]
                                 // and also put text/html on if it's HTML format
                                 widget()->clipboard_set_text(sel_type,
-                                                             {m_selection[sel]->str,
-                                                              m_selection[sel]->len});
+                                                             m_selection[sel]->str,
+                                                             m_selection[sel]->len);
                        }
                        g_string_free(m_selection[sel], TRUE);
                         m_selection[sel] = nullptr;
diff --git a/src/widget.cc b/src/widget.cc
index 5cc2b8fe..b84c0037 100644
--- a/src/widget.cc
+++ b/src/widget.cc
@@ -628,9 +628,10 @@ Widget::clipboard_request_text(ClipboardType type) noexcept
 
 void
 Widget::clipboard_set_text(ClipboardType type,
-                           std::string_view const& str) noexcept
+                           char const* str,
+                           size_t size) noexcept
 {
-        clipboard_get(type).set_text(str);
+        clipboard_get(type).set_text(str, size);
 }
 
 #if VTE_GTK == 4
diff --git a/src/widget.hh b/src/widget.hh
index c23e0422..fb50f161 100644
--- a/src/widget.hh
+++ b/src/widget.hh
@@ -383,7 +383,8 @@ public:
                                   ClipboardFormat format) noexcept;
         void clipboard_request_text(ClipboardType type) noexcept;
         void clipboard_set_text(ClipboardType type,
-                                std::string_view const& str) noexcept;
+                                char const* str,
+                                size_t size) noexcept;
 
         void paste_text(std::string_view const& text) { m_terminal->widget_paste(text); }
         void paste(vte::platform::ClipboardType type) { clipboard_request_text(type); }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]