summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Persch <chpe@src.gnome.org>2020-10-19 21:14:43 +0200
committerChristian Persch <chpe@src.gnome.org>2020-10-19 21:14:43 +0200
commit0136048d32d29412de3381828bb21f05563c799f (patch)
treef411d7dc6ae2c3b063cb111c527b385844214d94
parentbe6896d96dc5188fe8d9da80c41f2b04b1ead7ce (diff)
downloadvte-0136048d32d29412de3381828bb21f05563c799f.tar.gz
widget: Move clipboard handling to Widget
Move clipboard handling into its own Clipboard class to make Terminal independent of the platform's clipboard handling. [gtk4 preparation]
-rw-r--r--src/clipboard-gtk.cc305
-rw-r--r--src/clipboard-gtk.hh89
-rw-r--r--src/fwd.hh1
-rw-r--r--src/meson.build2
-rw-r--r--src/vte.cc230
-rw-r--r--src/vtegtk.cc19
-rw-r--r--src/vteinternal.hh126
-rw-r--r--src/widget.cc83
-rw-r--r--src/widget.hh39
9 files changed, 580 insertions, 314 deletions
diff --git a/src/clipboard-gtk.cc b/src/clipboard-gtk.cc
new file mode 100644
index 00000000..7be3f9a9
--- /dev/null
+++ b/src/clipboard-gtk.cc
@@ -0,0 +1,305 @@
+/*
+ * Copyright © 2020 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 by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "clipboard-gtk.hh"
+#include "widget.hh"
+#include "vteinternal.hh"
+
+#include <stdexcept>
+#include <utility>
+
+namespace vte::platform {
+
+// Note:
+// Each Clipboard is owned via std::shared_ptr by Widget, which drops that ref on unrealize.
+// The Clipboard keeps a std::weak_ref back on Widget, and converts that to a std::shared_ptr
+// via .lock() only when it wants to dispatch a callback.
+// Clipboard::Offer and Clipboard::Request own their Clipboard as a std::shared_ptr.
+
+Clipboard::Clipboard(Widget& delegate,
+ ClipboardType type) /* throws */
+ : m_delegate{delegate.weak_from_this()},
+ m_type{type}
+{
+ auto display = gtk_widget_get_display(delegate.gtk());
+
+ switch (type) {
+ case ClipboardType::PRIMARY:
+ m_clipboard = vte::glib::make_ref(gtk_clipboard_get_for_display(display,
+ GDK_SELECTION_PRIMARY));
+ break;
+ case ClipboardType::CLIPBOARD:
+ m_clipboard = vte::glib::make_ref(gtk_clipboard_get_for_display(display,
+ GDK_SELECTION_CLIPBOARD));
+ break;
+ }
+
+ if (!m_clipboard)
+ throw std::runtime_error{"Failed to create clipboard"};
+}
+
+class Clipboard::Offer {
+public:
+ Offer(Clipboard& clipboard,
+ OfferGetCallback get_callback,
+ OfferClearCallback clear_callback)
+ : m_clipboard{clipboard.shared_from_this()},
+ m_get_callback{get_callback},
+ m_clear_callback{clear_callback}
+ {
+ }
+
+ ~Offer() = default;
+
+ 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 clipboardship 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
+ }
+ }
+
+private:
+ std::shared_ptr<Clipboard> m_clipboard;
+ OfferGetCallback m_get_callback;
+ OfferClearCallback m_clear_callback;
+
+ void dispatch_get(ClipboardFormat format,
+ GtkSelectionData* data) noexcept
+ try
+ {
+ if (auto delegate = clipboard().m_delegate.lock()) {
+ auto str = (*delegate.*m_get_callback)(clipboard(), format);
+ if (!str)
+ return;
+
+ switch (format) {
+ case ClipboardFormat::TEXT:
+ // This makes yet another copy of the data... :(
+ gtk_selection_data_set_text(data, str->data(), str->size());
+ break;
+
+ case ClipboardFormat::HTML: {
+ auto [html, len] = text_to_utf16_mozilla(*str);
+
+ // This makes yet another copy of the data... :(
+ if (html) {
+ gtk_selection_data_set(data,
+ gtk_selection_data_get_target(data),
+ // or gdk_atom_intern_static_string("text/html"),
+ 16,
+ reinterpret_cast<guchar const*>(html.get()),
+ len);
+ }
+ break;
+ }
+ }
+ }
+ }
+ catch (...)
+ {
+ vte::log_exception();
+ }
+
+ void dispatch_clear() noexcept
+ try
+ {
+ if (auto delegate = clipboard().m_delegate.lock()) {
+ (*delegate.*m_clear_callback)(clipboard());
+ }
+ }
+ catch (...)
+ {
+ vte::log_exception();
+ }
+
+ static void
+ clipboard_get_cb(GtkClipboard* clipboard,
+ GtkSelectionData* data,
+ guint info,
+ void* user_data) noexcept
+ {
+ if (info != vte::to_integral(ClipboardFormat::TEXT) &&
+ info != vte::to_integral(ClipboardFormat::HTML))
+ return;
+
+ reinterpret_cast<Offer*>(user_data)->dispatch_get(ClipboardFormat(info), data);
+ }
+
+ static void
+ clipboard_clear_cb(GtkClipboard* clipboard,
+ void* user_data) noexcept
+ {
+ // Assume ownership of the Request, and delete it after dispatching the callback
+ auto offer = std::unique_ptr<Offer>{reinterpret_cast<Offer*>(user_data)};
+ offer->dispatch_clear();
+ }
+
+
+ static std::pair<GtkTargetEntry*, int>
+ targets_for_format(ClipboardFormat format)
+ {
+ switch (format) {
+ case vte::platform::ClipboardFormat::TEXT: {
+ static GtkTargetEntry *text_targets = nullptr;
+ static int n_text_targets;
+
+ if (text_targets == nullptr) {
+ auto list = gtk_target_list_new (nullptr, 0);
+ gtk_target_list_add_text_targets (list,
+ vte::to_integral(ClipboardFormat::TEXT));
+
+ text_targets = gtk_target_table_new_from_list (list, &n_text_targets);
+ gtk_target_list_unref (list);
+ }
+
+ return {text_targets, n_text_targets};
+ }
+
+ case vte::platform::ClipboardFormat::HTML: {
+ static GtkTargetEntry *html_targets = nullptr;
+ static int n_html_targets;
+
+ if (html_targets == nullptr) {
+ auto list = gtk_target_list_new (nullptr, 0);
+ gtk_target_list_add_text_targets (list,
+ vte::to_integral(ClipboardFormat::TEXT));
+ gtk_target_list_add (list,
+ gdk_atom_intern_static_string("text/html"),
+ 0,
+ vte::to_integral(ClipboardFormat::HTML));
+
+ html_targets = gtk_target_table_new_from_list (list, &n_html_targets);
+ gtk_target_list_unref (list);
+ }
+
+ return {html_targets, n_html_targets};
+ }
+ default:
+ g_assert_not_reached();
+ }
+ }
+
+
+ static std::pair<vte::glib::StringPtr, size_t>
+ text_to_utf16_mozilla(std::string_view const& str) noexcept
+ {
+ // Use g_convert() instead of g_utf8_to_utf16() since the former
+ // adds a BOM which Mozilla requires for text/html format.
+ auto len = size_t{};
+ auto data = g_convert(str.data(), str.size(),
+ "UTF-16", // conver to UTF-16
+ "UTF-8", // convert from UTF-8
+ nullptr, // out bytes_read
+ &len,
+ nullptr);
+ return {vte::glib::take_string(data), len};
+ }
+
+}; // class Clipboard::Offer
+
+class Clipboard::Request {
+public:
+ Request(Clipboard& clipboard,
+ RequestDoneCallback done_callback,
+ RequestFailedCallback failed_callback)
+ : m_clipboard{clipboard.shared_from_this()},
+ m_done_callback{done_callback},
+ m_failed_callback{failed_callback}
+ {
+ }
+
+ ~Request() = default;
+
+ auto& clipboard() const noexcept { return *m_clipboard; }
+
+ static void run(std::unique_ptr<Request> request) noexcept
+ {
+ auto platform = request->clipboard().platform();
+ gtk_clipboard_request_text(platform,
+ text_received_cb,
+ request.release());
+ }
+
+private:
+ std::shared_ptr<Clipboard> m_clipboard;
+ RequestDoneCallback m_done_callback;
+ RequestFailedCallback m_failed_callback;
+
+ void dispatch(char const *text) noexcept
+ try
+ {
+ if (auto delegate = clipboard().m_delegate.lock()) {
+ if (text)
+ (*delegate.*m_done_callback)(clipboard(), {text, strlen(text)});
+ else
+ (*delegate.*m_failed_callback)(clipboard());
+ }
+ }
+ catch (...)
+ {
+ vte::log_exception();
+ }
+
+ static void text_received_cb(GtkClipboard *clipboard,
+ char const* text,
+ gpointer data) noexcept
+ {
+ auto request = std::unique_ptr<Request>{reinterpret_cast<Request*>(data)};
+ request->dispatch(text);
+ }
+
+}; // class Clipboard::Request
+
+void
+Clipboard::offer_data(ClipboardFormat format,
+ OfferGetCallback get_callback,
+ OfferClearCallback clear_callback) /* throws */
+{
+ Offer::run(std::make_unique<Offer>(*this, get_callback, clear_callback), format);
+}
+
+void
+Clipboard::set_text(std::string_view const& text) noexcept
+{
+ gtk_clipboard_set_text(platform(), text.data(), text.size());
+}
+
+void
+Clipboard::request_text(RequestDoneCallback done_callback,
+ RequestFailedCallback failed_callback) /* throws */
+{
+ Request::run(std::make_unique<Request>(*this, done_callback, failed_callback));
+}
+
+} // namespace vte::platform
diff --git a/src/clipboard-gtk.hh b/src/clipboard-gtk.hh
new file mode 100644
index 00000000..c1c6b2ff
--- /dev/null
+++ b/src/clipboard-gtk.hh
@@ -0,0 +1,89 @@
+/*
+ * Copyright © 2020 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 by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <memory>
+#include <optional>
+#include <string>
+
+#include <gtk/gtk.h>
+
+#include "glib-glue.hh"
+#include "refptr.hh"
+#include "fwd.hh"
+
+namespace vte::platform {
+
+enum class ClipboardFormat {
+ TEXT,
+ HTML
+};
+
+enum class ClipboardType {
+ CLIPBOARD = 0,
+ PRIMARY = 1
+};
+
+class Clipboard : public std::enable_shared_from_this<Clipboard> {
+public:
+ Clipboard(Widget& delegate,
+ ClipboardType type) /* throws */;
+ ~Clipboard() = default;
+
+ Clipboard(Clipboard const&) = delete;
+ Clipboard(Clipboard&&) = delete;
+
+ Clipboard& operator=(Clipboard const&) = delete;
+ Clipboard& operator=(Clipboard&&) = delete;
+
+ constexpr auto type() const noexcept { return m_type; }
+
+ void disown() noexcept
+ {
+ m_delegate.reset();
+ }
+
+ using OfferGetCallback = std::optional<std::string_view>(Widget::*)(Clipboard const&,
+ ClipboardFormat format);
+ using OfferClearCallback = void (Widget::*)(Clipboard const&);
+ using RequestDoneCallback = void (Widget::*)(Clipboard const&,
+ std::string_view const&);
+ using RequestFailedCallback = void (Widget::*)(Clipboard const&);
+
+ void offer_data(ClipboardFormat format,
+ OfferGetCallback get_callback,
+ OfferClearCallback clear_callback) /* throws */;
+
+ void set_text(std::string_view const& text) noexcept;
+
+ void request_text(RequestDoneCallback done_callback,
+ RequestFailedCallback failed_callback) /* throws */;
+
+private:
+ vte::glib::RefPtr<GtkClipboard> m_clipboard;
+ std::weak_ptr<Widget> m_delegate;
+ ClipboardType m_type;
+
+ auto platform() const noexcept { return m_clipboard.get(); }
+
+ class Offer;
+ class Request;
+
+}; // class Clipboard
+
+} // namespace vte::platform
diff --git a/src/fwd.hh b/src/fwd.hh
index 2b7114c4..bf5906c8 100644
--- a/src/fwd.hh
+++ b/src/fwd.hh
@@ -27,6 +27,7 @@ class Pty;
namespace platform {
+class Clipboard;
class EventBase;
class KeyEvent;
class MouseEvent;
diff --git a/src/meson.build b/src/meson.build
index 248053c9..6e58f078 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -133,6 +133,8 @@ libvte_common_sources = debug_sources + glib_glue_sources + libc_glue_sources +
'cell.hh',
'chunk.cc',
'chunk.hh',
+ 'clipboard-gtk.cc',
+ 'clipboard-gtk.hh',
'color-triple.hh',
'cxx-utils.hh',
'drawing-cairo.cc',
diff --git a/src/vte.cc b/src/vte.cc
index 2cfa29da..c544cfd0 100644
--- a/src/vte.cc
+++ b/src/vte.cc
@@ -4765,7 +4765,7 @@ Terminal::widget_key_press(vte::platform::KeyEvent const& event)
handled = TRUE;
suppress_alt_esc = TRUE;
} else {
- widget_paste(vte::platform::ClipboardType::PRIMARY);
+ widget()->clipboard_request_text(vte::platform::ClipboardType::PRIMARY);
handled = TRUE;
suppress_alt_esc = TRUE;
}
@@ -5468,23 +5468,15 @@ Terminal::cell_is_selected_vis(vte::grid::column_t vcol,
}
void
-Terminal::widget_paste_received(char const* text)
+Terminal::widget_clipboard_text_received(vte::platform::Clipboard const& clipboard,
+ std::string_view const& data)
{
gchar *paste, *p;
gsize run;
unsigned char c;
- if (text == nullptr)
- return;
-
- gsize len = strlen(text);
- _vte_debug_print(VTE_DEBUG_SELECTION,
- "Pasting %" G_GSIZE_FORMAT " UTF-8 bytes.\n", len);
- // FIXMEchpe this cannot happen ever
- if (!g_utf8_validate(text, len, NULL)) {
- g_warning("Paste not valid UTF-8, dropping.");
- return;
- }
+ auto const len = data.size();
+ auto text = data.data();
/* Convert newlines to carriage returns, which more software
* is able to cope with (cough, pico, cough).
@@ -5989,103 +5981,45 @@ Terminal::match_hilite_update()
apply_mouse_cursor();
}
-/* Note that the clipboard has cleared. */
-static void
-clipboard_clear_cb(GtkClipboard *clipboard,
- gpointer user_data)
-{
- auto that = reinterpret_cast<vte::terminal::Terminal*>(user_data);
- that->widget_clipboard_cleared(clipboard);
-}
-
void
-Terminal::widget_clipboard_cleared(GtkClipboard *clipboard_)
+Terminal::widget_clipboard_data_clear(vte::platform::Clipboard const& clipboard)
{
if (m_changing_selection)
return;
- if (clipboard_ == get_clipboard(vte::platform::ClipboardType::PRIMARY)) {
+ switch (clipboard.type()) {
+ case vte::platform::ClipboardType::PRIMARY:
if (m_selection_owned[vte::to_integral(vte::platform::ClipboardType::PRIMARY)] &&
!m_selection_resolved.empty()) {
_vte_debug_print(VTE_DEBUG_SELECTION, "Lost selection.\n");
deselect_all();
}
- m_selection_owned[vte::to_integral(vte::platform::ClipboardType::PRIMARY)] = false;
- } else if (clipboard_ == get_clipboard(vte::platform::ClipboardType::CLIPBOARD)) {
- m_selection_owned[vte::to_integral(vte::platform::ClipboardType::CLIPBOARD)] = false;
+
+ [[fallthrough]];
+ case vte::platform::ClipboardType::CLIPBOARD:
+ m_selection_owned[vte::to_integral(clipboard.type())] = false;
+ break;
}
}
-/* Supply the selected text to the clipboard. */
-static void
-clipboard_copy_cb(GtkClipboard *clipboard,
- GtkSelectionData *data,
- guint info,
- gpointer user_data)
+std::optional<std::string_view>
+Terminal::widget_clipboard_data_get(vte::platform::Clipboard const& clipboard,
+ vte::platform::ClipboardFormat format)
{
- auto that = reinterpret_cast<vte::terminal::Terminal*>(user_data);
- that->widget_clipboard_requested(clipboard, data, info);
-}
+ auto const sel = vte::to_integral(clipboard.type());
-static char*
-text_to_utf16_mozilla(GString* text,
- gsize* len_ptr)
-{
- /* Use g_convert() instead of g_utf8_to_utf16() since the former
- * adds a BOM which Mozilla requires for text/html format.
- */
- return g_convert(text->str, text->len,
- "UTF-16", /* conver to UTF-16 */
- "UTF-8", /* convert from UTF-8 */
- nullptr /* out bytes_read */,
- len_ptr,
- nullptr);
-}
+ if (m_selection[sel] == nullptr)
+ return std::nullopt;
-void
-Terminal::widget_clipboard_requested(GtkClipboard *target_clipboard,
- GtkSelectionData *data,
- guint info)
-{
- for (auto sel_type : {vte::platform::ClipboardType::CLIPBOARD,
- vte::platform::ClipboardType::PRIMARY}) {
- auto const sel = vte::to_integral(sel_type);
- if (target_clipboard == get_clipboard(sel_type) &&
- m_selection[sel] != nullptr) {
- _VTE_DEBUG_IF(VTE_DEBUG_SELECTION) {
- int i;
- g_printerr("Setting selection %d (%" G_GSIZE_FORMAT " UTF-8 bytes.) for target %s\n",
- sel,
- m_selection[sel]->len,
- gdk_atom_name(gtk_selection_data_get_target(data)));
- char const* selection_text = m_selection[sel]->str;
- for (i = 0; selection_text[i] != '\0'; i++) {
- g_printerr("0x%04x ", selection_text[i]);
- if ((i & 0x7) == 0x7)
- g_printerr("\n");
- }
- g_printerr("\n");
- }
- if (info == VTE_TARGET_TEXT) {
- gtk_selection_data_set_text(data,
- m_selection[sel]->str,
- m_selection[sel]->len);
- } else if (info == VTE_TARGET_HTML) {
- gsize len;
- auto selection = text_to_utf16_mozilla(m_selection[sel], &len);
- // FIXMEchpe this makes yet another copy of the data... :(
- if (selection)
- gtk_selection_data_set(data,
- gdk_atom_intern_static_string("text/html"),
- 16,
- (const guchar *)selection,
- len);
- g_free(selection);
- } else {
- /* Not reached */
- }
- }
- }
+ _VTE_DEBUG_IF(VTE_DEBUG_SELECTION) {
+ g_printerr("Setting selection %d (%" G_GSIZE_FORMAT " UTF-8 bytes.) for target %s\n",
+ sel,
+ m_selection[sel]->len,
+ format == vte::platform::ClipboardFormat::HTML ? "HTML" : "TEXT");
+ _vte_debug_hexdump("Selection data", (uint8_t const*)m_selection[sel]->str, m_selection[sel]->len);
+ }
+
+ return std::string_view{m_selection[sel]->str, m_selection[sel]->len};
}
/* Convert the internal color code (either index or RGB) into RGB. */
@@ -6542,59 +6476,15 @@ Terminal::attributes_to_html(GString* text_string,
return string;
}
-static GtkTargetEntry*
-targets_for_format(VteFormat format,
- int *n_targets)
-{
- switch (format) {
- case VTE_FORMAT_TEXT: {
- static GtkTargetEntry *text_targets = nullptr;
- static int n_text_targets;
-
- if (text_targets == nullptr) {
- auto list = gtk_target_list_new (nullptr, 0);
- gtk_target_list_add_text_targets (list, VTE_TARGET_TEXT);
-
- text_targets = gtk_target_table_new_from_list (list, &n_text_targets);
- gtk_target_list_unref (list);
- }
-
- *n_targets = n_text_targets;
- return text_targets;
- }
-
- case VTE_FORMAT_HTML: {
- static GtkTargetEntry *html_targets = nullptr;
- static int n_html_targets;
-
- if (html_targets == nullptr) {
- auto list = gtk_target_list_new (nullptr, 0);
- gtk_target_list_add_text_targets (list, VTE_TARGET_TEXT);
- gtk_target_list_add (list,
- gdk_atom_intern_static_string("text/html"),
- 0,
- VTE_TARGET_HTML);
-
- html_targets = gtk_target_table_new_from_list (list, &n_html_targets);
- gtk_target_list_unref (list);
- }
-
- *n_targets = n_html_targets;
- return html_targets;
- }
- default:
- g_assert_not_reached();
- }
-}
-
/* Place the selected text onto the clipboard. Do this asynchronously so that
* we get notified when the selection we placed on the clipboard is replaced. */
void
Terminal::widget_copy(vte::platform::ClipboardType type,
- VteFormat format)
+ vte::platform::ClipboardFormat format)
{
/* Only put HTML on the CLIPBOARD, not PRIMARY */
- assert(type == vte::platform::ClipboardType::CLIPBOARD || format == VTE_FORMAT_TEXT);
+ assert(type == vte::platform::ClipboardType::CLIPBOARD ||
+ format == vte::platform::ClipboardFormat::TEXT);
/* Chuck old selected text and retrieve the newly-selected text. */
GArray *attributes = g_array_new(FALSE, TRUE, sizeof(struct _VteCharAttributes));
@@ -6612,7 +6502,7 @@ Terminal::widget_copy(vte::platform::ClipboardType type,
return;
}
- if (format == VTE_FORMAT_HTML) {
+ if (format == vte::platform::ClipboardFormat::HTML) {
m_selection[sel] = attributes_to_html(selection, attributes);
g_string_free(selection, TRUE);
} else {
@@ -6625,33 +6515,12 @@ Terminal::widget_copy(vte::platform::ClipboardType type,
_vte_debug_print(VTE_DEBUG_SELECTION,
"Assuming ownership of selection.\n");
- int n_targets;
- auto targets = targets_for_format(format, &n_targets);
-
- m_changing_selection = true;
- gtk_clipboard_set_with_data(get_clipboard(type),
- targets,
- n_targets,
- clipboard_copy_cb,
- clipboard_clear_cb,
- this);
- m_changing_selection = false;
-
- gtk_clipboard_set_can_store(get_clipboard(type), nullptr, 0);
m_selection_owned[sel] = true;
m_selection_format[sel] = format;
-}
-/* Paste from the given clipboard. */
-void
-Terminal::widget_paste(vte::platform::ClipboardType selection)
-{
- if (!m_input_enabled)
- return;
-
- _vte_debug_print(VTE_DEBUG_SELECTION, "Requesting clipboard contents.\n");
-
- m_paste_request.request_text(get_clipboard(selection), &Terminal::widget_paste_received, this);
+ m_changing_selection = true;
+ widget()->clipboard_offer_data(type, format);
+ m_changing_selection = false;
}
/* Confine coordinates into the visible area. Padding is already subtracted. */
@@ -6726,7 +6595,8 @@ Terminal::maybe_end_selection()
/* Copy only if something was selected. */
if (!m_selection_resolved.empty() &&
m_selecting_had_delta) {
- widget_copy(vte::platform::ClipboardType::PRIMARY, VTE_FORMAT_TEXT);
+ widget_copy(vte::platform::ClipboardType::PRIMARY,
+ vte::platform::ClipboardFormat::TEXT);
emit_selection_changed();
}
stop_autoscroll(); /* Required before setting m_selecting to false, see #105. */
@@ -6761,7 +6631,8 @@ Terminal::select_all()
_vte_debug_print(VTE_DEBUG_SELECTION, "Selecting *all* text.\n");
- widget_copy(vte::platform::ClipboardType::PRIMARY, VTE_FORMAT_TEXT);
+ widget_copy(vte::platform::ClipboardType::PRIMARY,
+ vte::platform::ClipboardFormat::TEXT);
emit_selection_changed();
invalidate_all();
@@ -6955,7 +6826,7 @@ Terminal::widget_mouse_press(vte::platform::MouseEvent const& event)
if ((m_modifiers & GDK_SHIFT_MASK) ||
m_mouse_tracking_mode == MouseTrackingMode::eNONE) {
if (widget()->primary_paste_enabled()) {
- widget_paste(vte::platform::ClipboardType::PRIMARY);
+ widget()->clipboard_request_text(vte::platform::ClipboardType::PRIMARY);
handled = true;
}
}
@@ -7760,9 +7631,6 @@ Terminal::Terminal(vte::platform::Widget* w,
gtk_widget_get_allocation(m_widget, &allocation);
set_allocated_rect(allocation);
- int i;
- GdkDisplay *display;
-
/* NOTE! We allocated zeroed memory, just fill in non-zero stuff. */
// FIXMEegmont make this store row indices only, maybe convert to a bitmap
@@ -7791,7 +7659,7 @@ Terminal::Terminal(vte::platform::Widget* w,
/* Set up the desired palette. */
set_colors_default();
- for (i = 0; i < VTE_PALETTE_SIZE; i++)
+ for (auto i = 0; i < VTE_PALETTE_SIZE; i++)
m_palette[i].sources[VTE_COLOR_SOURCE_ESCAPE].is_set = FALSE;
/* Dispatch unripe DCS (for now, just DECSIXEL) sequences,
@@ -7810,11 +7678,6 @@ Terminal::Terminal(vte::platform::Widget* w,
/* Default is 0, forces update in vte_terminal_set_scrollback_lines */
set_scrollback_lines(VTE_SCROLLBACK_INIT);
- /* Selection info. */
- display = gtk_widget_get_display(m_widget);
- m_clipboard[vte::to_integral(vte::platform::ClipboardType::CLIPBOARD)] = gtk_clipboard_get_for_display(display, GDK_SELECTION_CLIPBOARD);
- m_clipboard[vte::to_integral(vte::platform::ClipboardType::PRIMARY)] = gtk_clipboard_get_for_display(display, GDK_SELECTION_PRIMARY);
-
/* Initialize the saved cursor. */
save_cursor(&m_normal_screen);
save_cursor(&m_alternate_screen);
@@ -8049,10 +7912,10 @@ Terminal::~Terminal()
if (m_selection[sel] != nullptr) {
if (m_selection_owned[sel]) {
// FIXMEchpe we should check m_selection_format[sel]
- // and also put text/html on if it's VTE_FORMAT_HTML
- gtk_clipboard_set_text(get_clipboard(sel_type),
- m_selection[sel]->str,
- m_selection[sel]->len);
+ // 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});
}
g_string_free(m_selection[sel], TRUE);
m_selection[sel] = nullptr;
@@ -10169,7 +10032,8 @@ Terminal::select_text(vte::grid::column_t start_col,
m_selecting_had_delta = true;
m_selection_resolved.set ({ start_row, start_col },
{ end_row, end_col });
- widget_copy(vte::platform::ClipboardType::PRIMARY, VTE_FORMAT_TEXT);
+ widget_copy(vte::platform::ClipboardType::PRIMARY,
+ vte::platform::ClipboardFormat::TEXT);
emit_selection_changed();
invalidate_rows(start_row, end_row);
diff --git a/src/vtegtk.cc b/src/vtegtk.cc
index 79950643..cb35ceaa 100644
--- a/src/vtegtk.cc
+++ b/src/vtegtk.cc
@@ -175,6 +175,16 @@ valid_color(GdkRGBA const* color) noexcept
color->alpha >= 0. && color->alpha <= 1.;
}
+static vte::platform::ClipboardFormat
+clipboard_format_from_vte(VteFormat format)
+{
+ switch (format) {
+ case VTE_FORMAT_TEXT: return vte::platform::ClipboardFormat::TEXT;
+ case VTE_FORMAT_HTML: return vte::platform::ClipboardFormat::HTML;
+ default: throw std::runtime_error{"Unknown VteFormat enum value"};
+ }
+}
+
static void
vte_terminal_set_hadjustment(VteTerminal *terminal,
GtkAdjustment *adjustment) noexcept
@@ -231,7 +241,8 @@ static void
vte_terminal_real_copy_clipboard(VteTerminal *terminal) noexcept
try
{
- WIDGET(terminal)->copy(vte::platform::ClipboardType::CLIPBOARD, VTE_FORMAT_TEXT);
+ WIDGET(terminal)->copy(vte::platform::ClipboardType::CLIPBOARD,
+ vte::platform::ClipboardFormat::TEXT);
}
catch (...)
{
@@ -2402,7 +2413,8 @@ try
g_return_if_fail(VTE_IS_TERMINAL(terminal));
g_return_if_fail(format == VTE_FORMAT_TEXT || format == VTE_FORMAT_HTML);
- WIDGET(terminal)->copy(vte::platform::ClipboardType::CLIPBOARD, format);
+ WIDGET(terminal)->copy(vte::platform::ClipboardType::CLIPBOARD,
+ clipboard_format_from_vte(format));
}
catch (...)
{
@@ -2422,7 +2434,8 @@ try
{
g_return_if_fail(VTE_IS_TERMINAL(terminal));
_vte_debug_print(VTE_DEBUG_SELECTION, "Copying to PRIMARY.\n");
- WIDGET(terminal)->copy(vte::platform::ClipboardType::PRIMARY, VTE_FORMAT_TEXT);
+ WIDGET(terminal)->copy(vte::platform::ClipboardType::PRIMARY,
+ vte::platform::ClipboardFormat::TEXT);
}
catch (...)
{
diff --git a/src/vteinternal.hh b/src/vteinternal.hh
index a590b13c..08e20c01 100644
--- a/src/vteinternal.hh
+++ b/src/vteinternal.hh
@@ -34,6 +34,7 @@
#include "glib-glue.hh"
#include "debug.h"
+#include "clipboard-gtk.hh"
#include "drawing-cairo.hh"
#include "vtedefines.hh"
#include "vtetypes.hh"
@@ -46,6 +47,7 @@
#include "modes.hh"
#include "tabstops.hh"
#include "refptr.hh"
+#include "fwd.hh"
#include "vtepcre2.h"
#include "vteregexinternal.hh"
@@ -135,112 +137,10 @@ public:
} saved;
};
-/* Until the selection can be generated on demand, let's not enable this on stable */
-#include "vte/vteversion.h"
-#if (VTE_MINOR_VERSION % 2) == 0
-#undef HTML_SELECTION
-#else
-#define HTML_SELECTION
-#endif
-
-/* Used in the GtkClipboard API, to distinguish requests for HTML and TEXT
- * contents of a clipboard */
-typedef enum {
- VTE_TARGET_TEXT,
- VTE_TARGET_HTML,
- LAST_VTE_TARGET
-} VteSelectionTarget;
-
struct vte_scrolling_region {
int start, end;
};
-template <class T>
-class ClipboardTextRequestGtk {
-public:
- typedef void (T::* Callback)(char const*);
-
- ClipboardTextRequestGtk() : m_request(nullptr) { }
- ~ClipboardTextRequestGtk() { cancel(); }
-
- void request_text(GtkClipboard *clipboard,
- Callback callback,
- T* that)
- {
- cancel();
- new Request(clipboard, callback, that, &m_request);
- }
-
-private:
-
- class Request {
- public:
- Request(GtkClipboard *clipboard,
- Callback callback,
- T* that,
- Request** location) :
- m_callback(callback),
- m_that(that),
- m_location(location)
- {
- /* We need to store this here instead of doing it after the |new| above,
- * since gtk_clipboard_request_text may dispatch the callback
- * immediately or only later, with no way to know this beforehand.
- */
- *m_location = this;
- gtk_clipboard_request_text(clipboard, text_received, this);
- }
-
- ~Request()
- {
- invalidate();
- }
-
- void cancel()
- {
- invalidate();
- m_that = nullptr;
- m_location = nullptr;
- }
-
- private:
- Callback m_callback;
- T *m_that;
- Request** m_location;
-
- void invalidate()
- {
- if (m_that && m_location)
- *m_location = nullptr;
- }
-
- void dispatch(char const *text)
- {
- if (m_that) {
- g_assert(m_location == nullptr || *m_location == this);
-
- (m_that->*m_callback)(text);
- }
- }
-
- static void text_received(GtkClipboard *clipboard, char const* text, gpointer data) {
- Request* request = reinterpret_cast<Request*>(data);
- request->dispatch(text);
- delete request;
- }
- };
-
-private:
- void cancel()
- {
- if (m_request)
- m_request->cancel();
- g_assert(m_request == nullptr);
- }
-
- Request *m_request;
-};
-
namespace vte {
namespace platform {
@@ -490,13 +390,8 @@ public:
/* Clipboard data information. */
bool m_selection_owned[2]{false, false};
bool m_changing_selection{false};
- VteFormat m_selection_format[2];
+ vte::platform::ClipboardFormat m_selection_format[2];
GString *m_selection[2]; // FIXMEegmont rename this so that m_selection_resolved can become m_selection?
- GtkClipboard *m_clipboard[2];
-
- auto get_clipboard(vte::platform::ClipboardType type) const noexcept { return m_clipboard[vte::to_integral(type)]; }
-
- ClipboardTextRequestGtk<Terminal> m_paste_request;
/* Miscellaneous options. */
EraseMode m_backspace_binding{EraseMode::eAUTO};
@@ -944,14 +839,15 @@ public:
void set_border_padding(GtkBorder const* padding);
void set_cursor_aspect(float aspect);
- void widget_paste(vte::platform::ClipboardType selection);
void widget_copy(vte::platform::ClipboardType selection,
- VteFormat format);
- void widget_paste_received(char const* text);
- void widget_clipboard_cleared(GtkClipboard *clipboard);
- void widget_clipboard_requested(GtkClipboard *target_clipboard,
- GtkSelectionData *data,
- guint info);
+ vte::platform::ClipboardFormat format);
+
+ void widget_clipboard_text_received(vte::platform::Clipboard const& clipboard,
+ std::string_view const& text);
+
+ std::optional<std::string_view> widget_clipboard_data_get(vte::platform::Clipboard const& clipboard,
+ vte::platform::ClipboardFormat format);
+ void widget_clipboard_data_clear(vte::platform::Clipboard const& clipboard);
void widget_set_vadjustment(vte::glib::RefPtr<GtkAdjustment>&& adjustment);
diff --git a/src/widget.cc b/src/widget.cc
index c4dfc53a..d56e900d 100644
--- a/src/widget.cc
+++ b/src/widget.cc
@@ -221,6 +221,75 @@ Widget::set_cursor(Cursor const& cursor) noexcept
g_object_unref(gdk_cursor);
}
+Clipboard&
+Widget::clipboard_get(ClipboardType type) const
+{
+ switch (type) {
+ case ClipboardType::PRIMARY: return *m_clipboard;
+ case ClipboardType::CLIPBOARD: return *m_primary_clipboard;
+ default: g_assert_not_reached(); throw std::runtime_error{""}; break;
+ }
+}
+
+std::optional<std::string_view>
+Widget::clipboard_data_get_cb(Clipboard const& clipboard,
+ ClipboardFormat format)
+{
+ return terminal()->widget_clipboard_data_get(clipboard, format);
+}
+
+void
+Widget::clipboard_data_clear_cb(Clipboard const& clipboard)
+{
+ terminal()->widget_clipboard_data_clear(clipboard);
+}
+
+void
+Widget::clipboard_request_received_cb(Clipboard const& clipboard,
+ std::string_view const& text)
+{
+ terminal()->widget_clipboard_text_received(clipboard, text);
+}
+
+void
+Widget::clipboard_request_failed_cb(Clipboard const& clipboard)
+{
+ gtk_widget_error_bell(gtk());
+}
+
+void
+Widget::clipboard_offer_data(ClipboardType type,
+ ClipboardFormat format) noexcept
+{
+ try {
+ clipboard_get(type).offer_data(format,
+ &Widget::clipboard_data_get_cb,
+ &Widget::clipboard_data_clear_cb);
+ } catch (...) {
+ /* Let the caller know the request failed */
+ terminal()->widget_clipboard_data_clear(clipboard_get(type));
+ }
+}
+
+void
+Widget::clipboard_request_text(ClipboardType type) noexcept
+{
+ try {
+ clipboard_get(type).request_text(&Widget::clipboard_request_received_cb,
+ &Widget::clipboard_request_failed_cb);
+ } catch (...) {
+ /* Let the caller know the request failed */
+ clipboard_request_failed_cb(clipboard_get(type));
+ }
+}
+
+void
+Widget::clipboard_set_text(ClipboardType type,
+ std::string_view const& str) noexcept
+{
+ clipboard_get(type).set_text(str);
+}
+
void
Widget::constructed() noexcept
{
@@ -499,6 +568,9 @@ Widget::realize() noexcept
G_CALLBACK(im_delete_surrounding_cb), this);
gtk_im_context_set_use_preedit(m_im_context.get(), true);
+ m_clipboard = std::make_shared<Clipboard>(*this, ClipboardType::CLIPBOARD);
+ m_primary_clipboard = std::make_shared<Clipboard>(*this, ClipboardType::PRIMARY);
+
m_terminal->widget_realize();
}
@@ -650,6 +722,17 @@ Widget::unrealize() noexcept
{
m_terminal->widget_unrealize();
+ if (m_clipboard) {
+ terminal()->widget_clipboard_data_clear(*m_clipboard);
+ m_clipboard->disown();
+ }
+ if (m_primary_clipboard) {
+ terminal()->widget_clipboard_data_clear(*m_primary_clipboard);
+ m_primary_clipboard->disown();
+ }
+ m_clipboard.reset();
+ m_primary_clipboard.reset();
+
m_default_cursor.reset();
m_invisible_cursor.reset();
m_mousing_cursor.reset();
diff --git a/src/widget.hh b/src/widget.hh
index dfdff894..49d05a5a 100644
--- a/src/widget.hh
+++ b/src/widget.hh
@@ -28,6 +28,9 @@
#include "vteinternal.hh"
#include "fwd.hh"
+
+#include "clipboard-gtk.hh"
+#include "regex.hh"
#include "refptr.hh"
namespace vte {
@@ -40,11 +43,6 @@ class Terminal;
namespace platform {
-enum class ClipboardType {
- CLIPBOARD = 0,
- PRIMARY = 1
-};
-
class EventBase {
friend class vte::platform::Widget;
friend class Terminal;
@@ -304,14 +302,17 @@ public:
void grab_focus() noexcept { gtk_widget_grab_focus(gtk()); }
bool primary_paste_enabled() const noexcept;
- void paste(vte::platform::ClipboardType sel) noexcept { m_terminal->widget_paste(sel); }
- void copy(vte::platform::ClipboardType sel,
- VteFormat format) noexcept { m_terminal->widget_copy(sel, format); }
- void paste_received(char const* text) noexcept { m_terminal->widget_paste_received(text); }
- void clipboard_cleared(GtkClipboard *clipboard) noexcept { m_terminal->widget_clipboard_cleared(clipboard); }
- void clipboard_requested(GtkClipboard *target_clipboard,
- GtkSelectionData *data,
- guint info) noexcept { m_terminal->widget_clipboard_requested(target_clipboard, data, info); }
+
+ Clipboard& clipboard_get(ClipboardType type) const;
+ void clipboard_offer_data(ClipboardType type,
+ ClipboardFormat format) noexcept;
+ void clipboard_request_text(ClipboardType type) noexcept;
+ void clipboard_set_text(ClipboardType type,
+ std::string_view const& str) noexcept;
+
+ void paste(vte::platform::ClipboardType type) { clipboard_request_text(type); }
+ void copy(vte::platform::ClipboardType type,
+ vte::platform::ClipboardFormat format) noexcept { m_terminal->widget_copy(type, format); }
void screen_changed (GdkScreen *previous_screen) noexcept;
void settings_changed() noexcept;
@@ -438,6 +439,14 @@ private:
KeyEvent key_event_from_gdk(GdkEventKey* event) const;
MouseEvent mouse_event_from_gdk(GdkEvent* event) const /* throws */;
+ void clipboard_request_received_cb(Clipboard const& clipboard,
+ std::string_view const& text);
+ void clipboard_request_failed_cb(Clipboard const& clipboard);
+
+ std::optional<std::string_view> clipboard_data_get_cb(Clipboard const& clipboard,
+ ClipboardFormat format);
+ void clipboard_data_clear_cb(Clipboard const& clipboard);
+
GtkWidget* m_widget;
vte::terminal::Terminal* m_terminal;
@@ -457,6 +466,10 @@ private:
/* PTY */
vte::glib::RefPtr<VtePty> m_pty;
+ /* Clipboard */
+ std::shared_ptr<Clipboard> m_clipboard;
+ std::shared_ptr<Clipboard> m_primary_clipboard;
+
/* Misc */
std::optional<std::string> m_word_char_exceptions{};