summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Persch <chpe@src.gnome.org>2022-09-01 18:34:03 +0200
committerChristian Persch <chpe@src.gnome.org>2022-09-01 18:34:03 +0200
commitb72b397be45045bc5464ba8ed889f6fb2b39989a (patch)
treecf0177eb3aaf3d4862c303ec873b1bbb5afa57b6
parentfd6d6226272cda3e8c4d4a58e84c796656264566 (diff)
downloadvte-b72b397be45045bc5464ba8ed889f6fb2b39989a.tar.gz
widget: Implement clipboard for gtk4
Fixes: https://gitlab.gnome.org/GNOME/vte/-/issues/2557
-rw-r--r--src/clipboard-gtk.cc536
-rw-r--r--src/clipboard-gtk.hh15
-rw-r--r--src/glib-glue.hh1
-rw-r--r--src/gtk-glue.hh1
-rw-r--r--src/vte.cc4
-rw-r--r--src/widget.cc5
-rw-r--r--src/widget.hh3
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); }