[vte] widget: Move clipboard handling to Widget



commit 0136048d32d29412de3381828bb21f05563c799f
Author: Christian Persch <chpe src gnome org>
Date:   Mon Oct 19 21:14:43 2020 +0200

    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]

 src/clipboard-gtk.cc | 305 +++++++++++++++++++++++++++++++++++++++++++++++++++
 src/clipboard-gtk.hh |  89 +++++++++++++++
 src/fwd.hh           |   1 +
 src/meson.build      |   2 +
 src/vte.cc           | 230 ++++++++------------------------------
 src/vtegtk.cc        |  19 +++-
 src/vteinternal.hh   | 126 ++-------------------
 src/widget.cc        |  83 ++++++++++++++
 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
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{};
 



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