diff options
author | Christian Persch <chpe@src.gnome.org> | 2020-10-19 21:14:43 +0200 |
---|---|---|
committer | Christian Persch <chpe@src.gnome.org> | 2020-10-19 21:14:43 +0200 |
commit | 0136048d32d29412de3381828bb21f05563c799f (patch) | |
tree | f411d7dc6ae2c3b063cb111c527b385844214d94 | |
parent | be6896d96dc5188fe8d9da80c41f2b04b1ead7ce (diff) | |
download | vte-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.cc | 305 | ||||
-rw-r--r-- | src/clipboard-gtk.hh | 89 | ||||
-rw-r--r-- | src/fwd.hh | 1 | ||||
-rw-r--r-- | src/meson.build | 2 | ||||
-rw-r--r-- | src/vte.cc | 230 | ||||
-rw-r--r-- | src/vtegtk.cc | 19 | ||||
-rw-r--r-- | src/vteinternal.hh | 126 | ||||
-rw-r--r-- | src/widget.cc | 83 | ||||
-rw-r--r-- | src/widget.hh | 39 |
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 @@ -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', @@ -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{}; |