[Spice-devel] [PATCH spice-gtk 3/3] Move clipboard handling to SpiceGtkSession

Hans de Goede hdegoede at redhat.com
Tue Oct 4 07:20:13 PDT 2011


This fixes copy and paste with multi-monitor guests. There still is
one small issue left with this patch, changing the setting for auto-clipboard
in one spicy window, does not get reflected in the Options menu of the
other spicy windows.

This can be fixed by listening to the notify signal, this also requires
SpiceDisplay to listen to property changes on its SpiceGtkSession and
then do a g_object_set on itself to update its own property (and also
emit its own notify signal.

I'll write a separate patch for this.

Signed-off-by: Hans de Goede <hdegoede at redhat.com>
---
 doc/reference/spice-gtk-sections.txt |    2 +
 gtk/map-file                         |    2 +
 gtk/spice-gtk-session.c              |  590 +++++++++++++++++++++++++++++++++-
 gtk/spice-gtk-session.h              |    2 +
 gtk/spice-widget-priv.h              |   15 +-
 gtk/spice-widget.c                   |  465 ++-------------------------
 6 files changed, 616 insertions(+), 460 deletions(-)

diff --git a/doc/reference/spice-gtk-sections.txt b/doc/reference/spice-gtk-sections.txt
index d5e8e70..56ae829 100644
--- a/doc/reference/spice-gtk-sections.txt
+++ b/doc/reference/spice-gtk-sections.txt
@@ -292,6 +292,8 @@ SpiceUsbDeviceManagerPrivate
 SpiceGtkSession
 SpiceGtkSessionClass
 spice_gtk_session_get
+spice_gtk_session_copy_to_guest
+spice_gtk_session_paste_from_guest
 <SUBSECTION Standard>
 SPICE_GTK_SESSION
 SPICE_IS_GTK_SESSION
diff --git a/gtk/map-file b/gtk/map-file
index bedfdb9..789a507 100644
--- a/gtk/map-file
+++ b/gtk/map-file
@@ -71,6 +71,8 @@ spice_session_set_gtk_session;
 spice_session_verify_get_type;
 spice_gtk_session_get;
 spice_gtk_session_get_type;
+spice_gtk_session_copy_to_guest;
+spice_gtk_session_paste_from_guest;
 spice_set_session_option;
 spice_smartcard_channel_get_type;
 spice_smartcard_manager_get;
diff --git a/gtk/spice-gtk-session.c b/gtk/spice-gtk-session.c
index a4bc106..8b73e10 100644
--- a/gtk/spice-gtk-session.c
+++ b/gtk/spice-gtk-session.c
@@ -17,11 +17,25 @@
 */
 
 #include "config.h"
+#include <gtk/gtk.h>
+#include <spice/vd_agent.h>
 #include "spice-session-priv.h"
 #include "spice-gtk-session.h"
 
+#define CLIPBOARD_LAST (VD_AGENT_CLIPBOARD_SELECTION_SECONDARY + 1)
+
 struct _SpiceGtkSessionPrivate {
-    SpiceSession *session;
+    SpiceSession            *session;
+    SpiceMainChannel        *main;
+    gboolean                auto_clipboard_enable;
+    GtkClipboard            *clipboard;
+    GtkClipboard            *clipboard_primary;
+    GtkTargetEntry          *clip_targets[CLIPBOARD_LAST];
+    guint                   nclip_targets[CLIPBOARD_LAST];
+    gboolean                clip_hasdata[CLIPBOARD_LAST];
+    gboolean                clip_grabbed[CLIPBOARD_LAST];
+    gboolean                clipboard_by_guest[CLIPBOARD_LAST];
+    gboolean                clipboard_selfgrab_pending[CLIPBOARD_LAST];
 };
 
 /**
@@ -53,6 +67,16 @@ struct _SpiceGtkSessionPrivate {
  */
 
 /* ------------------------------------------------------------------ */
+/* Prototypes for private functions */
+static void clipboard_owner_change(GtkClipboard *clipboard,
+                                   GdkEventOwnerChange *event,
+                                   gpointer user_data);
+static void channel_new(SpiceSession *session, SpiceChannel *channel,
+                        gpointer user_data);
+static void channel_destroy(SpiceSession *session, SpiceChannel *channel,
+                            gpointer user_data);
+
+/* ------------------------------------------------------------------ */
 /* gobject glue                                                       */
 
 #define SPICE_GTK_SESSION_GET_PRIVATE(obj) \
@@ -64,6 +88,7 @@ G_DEFINE_TYPE (SpiceGtkSession, spice_gtk_session, G_TYPE_OBJECT);
 enum {
     PROP_0,
     PROP_SESSION,
+    PROP_AUTO_CLIPBOARD,
 };
 
 static void spice_gtk_session_init(SpiceGtkSession *gtk_session)
@@ -73,6 +98,13 @@ static void spice_gtk_session_init(SpiceGtkSession *gtk_session)
     SPICE_DEBUG("New gtk session (compiled from package " PACKAGE_STRING ")");
     s = gtk_session->priv = SPICE_GTK_SESSION_GET_PRIVATE(gtk_session);
     memset(s, 0, sizeof(*s));
+
+    s->clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
+    g_signal_connect(G_OBJECT(s->clipboard), "owner-change",
+                     G_CALLBACK(clipboard_owner_change), gtk_session);
+    s->clipboard_primary = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
+    g_signal_connect(G_OBJECT(s->clipboard_primary), "owner-change",
+                     G_CALLBACK(clipboard_owner_change), gtk_session);
 }
 
 static GObject *
@@ -82,6 +114,9 @@ spice_gtk_session_constructor(GType                  gtype,
 {
     GObject *obj;
     SpiceGtkSession *gtk_session;
+    SpiceGtkSessionPrivate *s;
+    GList *list;
+    GList *it;
 
     {
         /* Always chain up to the parent constructor */
@@ -91,20 +126,50 @@ spice_gtk_session_constructor(GType                  gtype,
     }
 
     gtk_session = SPICE_GTK_SESSION(obj);
-    if (!gtk_session->priv->session)
+    s = SPICE_GTK_SESSION_GET_PRIVATE(gtk_session);
+    if (!s->session)
         g_error("SpiceGtKSession constructed without a session");
 
+    g_signal_connect(s->session, "channel-new",
+                     G_CALLBACK(channel_new), gtk_session);
+    g_signal_connect(s->session, "channel-destroy",
+                     G_CALLBACK(channel_destroy), gtk_session);
+    list = spice_session_get_channels(s->session);
+    for (it = g_list_first(list); it != NULL; it = g_list_next(it)) {
+        channel_new(s->session, it->data, (gpointer*)gtk_session);
+    }
+    g_list_free(list);
+
     return obj;
 }
 
 static void spice_gtk_session_dispose(GObject *gobject)
 {
-#if 0 /* Unused for now */
     SpiceGtkSession *gtk_session = SPICE_GTK_SESSION(gobject);
     SpiceGtkSessionPrivate *s = SPICE_GTK_SESSION_GET_PRIVATE(gtk_session);
-#endif
 
     /* release stuff */
+    if (s->clipboard) {
+        g_signal_handlers_disconnect_by_func(s->clipboard,
+                G_CALLBACK(clipboard_owner_change), gtk_session);
+        s->clipboard = NULL;
+    }
+
+    if (s->clipboard_primary) {
+        g_signal_handlers_disconnect_by_func(s->clipboard_primary,
+                G_CALLBACK(clipboard_owner_change), gtk_session);
+        s->clipboard_primary = NULL;
+    }
+
+    if (s->session) {
+        g_signal_handlers_disconnect_by_func(s->session,
+                                             G_CALLBACK(channel_new),
+                                             gtk_session);
+        g_signal_handlers_disconnect_by_func(s->session,
+                                             G_CALLBACK(channel_destroy),
+                                             gtk_session);
+        s->session = NULL;
+    }
 
     /* Chain up to the parent class */
     if (G_OBJECT_CLASS(spice_gtk_session_parent_class)->dispose)
@@ -113,12 +178,15 @@ static void spice_gtk_session_dispose(GObject *gobject)
 
 static void spice_gtk_session_finalize(GObject *gobject)
 {
-#if 0 /* Unused for now */
     SpiceGtkSession *gtk_session = SPICE_GTK_SESSION(gobject);
     SpiceGtkSessionPrivate *s = SPICE_GTK_SESSION_GET_PRIVATE(gtk_session);
-#endif
+    int i;
 
     /* release stuff */
+    for (i = 0; i < CLIPBOARD_LAST; ++i) {
+        g_free(s->clip_targets[i]);
+        s->clip_targets[i] = NULL;
+    }
 
     /* Chain up to the parent class */
     if (G_OBJECT_CLASS(spice_gtk_session_parent_class)->finalize)
@@ -137,6 +205,9 @@ static void spice_gtk_session_get_property(GObject    *gobject,
     case PROP_SESSION:
         g_value_set_object(value, s->session);
 	break;
+    case PROP_AUTO_CLIPBOARD:
+        g_value_set_boolean(value, s->auto_clipboard_enable);
+        break;
     default:
 	G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
 	break;
@@ -155,6 +226,9 @@ static void spice_gtk_session_set_property(GObject      *gobject,
     case PROP_SESSION:
         s->session = g_value_get_object(value);
         break;
+    case PROP_AUTO_CLIPBOARD:
+        s->auto_clipboard_enable = g_value_get_boolean(value);
+        break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
         break;
@@ -187,9 +261,467 @@ static void spice_gtk_session_class_init(SpiceGtkSessionClass *klass)
                              G_PARAM_CONSTRUCT_ONLY |
                              G_PARAM_STATIC_STRINGS));
 
+    /**
+     * SpiceGtkSession:auto-clipboard:
+     *
+     * When this is true the clipboard gets automatically shared between host
+     * and guest.
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_AUTO_CLIPBOARD,
+         g_param_spec_boolean("auto-clipboard",
+                              "Auto clipboard",
+                              "Automatically relay clipboard changes between "
+                              "host and guest.",
+                              TRUE,
+                              G_PARAM_READWRITE |
+                              G_PARAM_CONSTRUCT |
+                              G_PARAM_STATIC_STRINGS));
+
     g_type_class_add_private(klass, sizeof(SpiceGtkSessionPrivate));
 }
 
+/* ---------------------------------------------------------------- */
+/* private functions (clipboard related)                            */
+
+static GtkClipboard* get_clipboard_from_selection(SpiceGtkSessionPrivate *s,
+                                                  guint selection)
+{
+    if (selection == VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD) {
+        return s->clipboard;
+    } else if (selection == VD_AGENT_CLIPBOARD_SELECTION_PRIMARY) {
+        return s->clipboard_primary;
+    } else {
+        g_warning("Unhandled clipboard selection: %d", selection);
+        return NULL;
+    }
+}
+
+static gint get_selection_from_clipboard(SpiceGtkSessionPrivate *s,
+                                         GtkClipboard* cb)
+{
+    if (cb == s->clipboard) {
+        return VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD;
+    } else if (cb == s->clipboard_primary) {
+        return VD_AGENT_CLIPBOARD_SELECTION_PRIMARY;
+    } else {
+        g_warning("Unhandled clipboard");
+        return -1;
+    }
+}
+
+static const struct {
+    const char  *xatom;
+    uint32_t    vdagent;
+    uint32_t    flags;
+} atom2agent[] = {
+    {
+        .vdagent = VD_AGENT_CLIPBOARD_UTF8_TEXT,
+        .xatom   = "UTF8_STRING",
+    },{
+        .vdagent = VD_AGENT_CLIPBOARD_UTF8_TEXT,
+        .xatom   = "text/plain;charset=utf-8"
+    },{
+        .vdagent = VD_AGENT_CLIPBOARD_UTF8_TEXT,
+        .xatom   = "STRING"
+    },{
+        .vdagent = VD_AGENT_CLIPBOARD_UTF8_TEXT,
+        .xatom   = "TEXT"
+    },{
+        .vdagent = VD_AGENT_CLIPBOARD_UTF8_TEXT,
+        .xatom   = "text/plain"
+    },{
+        .vdagent = VD_AGENT_CLIPBOARD_IMAGE_PNG,
+        .xatom   = "image/png"
+    },{
+        .vdagent = VD_AGENT_CLIPBOARD_IMAGE_BMP,
+        .xatom   = "image/bmp"
+    },{
+        .vdagent = VD_AGENT_CLIPBOARD_IMAGE_BMP,
+        .xatom   = "image/x-bmp"
+    },{
+        .vdagent = VD_AGENT_CLIPBOARD_IMAGE_BMP,
+        .xatom   = "image/x-MS-bmp"
+    },{
+        .vdagent = VD_AGENT_CLIPBOARD_IMAGE_BMP,
+        .xatom   = "image/x-win-bitmap"
+    },{
+        .vdagent = VD_AGENT_CLIPBOARD_IMAGE_TIFF,
+        .xatom   = "image/tiff"
+    },{
+        .vdagent = VD_AGENT_CLIPBOARD_IMAGE_JPG,
+        .xatom   = "image/jpeg"
+    }
+};
+
+static void clipboard_get_targets(GtkClipboard *clipboard,
+                                  GdkAtom *atoms,
+                                  gint n_atoms,
+                                  gpointer user_data)
+{
+    SpiceGtkSession *gtk_session = user_data;
+    SpiceGtkSessionPrivate *s = SPICE_GTK_SESSION_GET_PRIVATE(gtk_session);
+    guint32 types[SPICE_N_ELEMENTS(atom2agent)];
+    char *name;
+    int a, m, t;
+    int selection;
+
+    selection = get_selection_from_clipboard(s, clipboard);
+    g_return_if_fail(selection != -1);
+    g_return_if_fail(s->main != NULL);
+
+    SPICE_DEBUG("%s:", __FUNCTION__);
+    if (spice_util_get_debug()) {
+        for (a = 0; a < n_atoms; a++) {
+            name = gdk_atom_name(atoms[a]);
+            SPICE_DEBUG(" \"%s\"", name);
+            g_free(name);
+        }
+    }
+
+    memset(types, 0, sizeof(types));
+    for (a = 0; a < n_atoms; a++) {
+        name = gdk_atom_name(atoms[a]);
+        for (m = 0; m < SPICE_N_ELEMENTS(atom2agent); m++) {
+            if (strcasecmp(name, atom2agent[m].xatom) != 0) {
+                continue;
+            }
+            /* found match */
+            for (t = 0; t < SPICE_N_ELEMENTS(atom2agent); t++) {
+                if (types[t] == atom2agent[m].vdagent) {
+                    /* type already in list */
+                    break;
+                }
+                if (types[t] == 0) {
+                    /* add type to empty slot */
+                    types[t] = atom2agent[m].vdagent;
+                    break;
+                }
+            }
+            break;
+        }
+        g_free(name);
+    }
+    for (t = 0; t < SPICE_N_ELEMENTS(atom2agent); t++) {
+        if (types[t] == 0) {
+            break;
+        }
+    }
+    if (!s->clip_grabbed[selection] && t > 0) {
+        s->clip_grabbed[selection] = TRUE;
+        spice_main_clipboard_selection_grab(s->main, selection, types, t);
+        /* Sending a grab causes the agent to do an impicit release */
+        s->nclip_targets[selection] = 0;
+    }
+}
+
+static void clipboard_owner_change(GtkClipboard        *clipboard,
+                                   GdkEventOwnerChange *event,
+                                   gpointer            user_data)
+{
+    SpiceGtkSession *gtk_session = user_data;
+    SpiceGtkSessionPrivate *s = SPICE_GTK_SESSION_GET_PRIVATE(gtk_session);
+    int selection;
+
+    selection = get_selection_from_clipboard(s, clipboard);
+    g_return_if_fail(selection != -1);
+
+    if (s->main == NULL)
+        return;
+
+    if (s->clip_grabbed[selection]) {
+        s->clip_grabbed[selection] = FALSE;
+        spice_main_clipboard_selection_release(s->main, selection);
+    }
+
+    switch (event->reason) {
+    case GDK_OWNER_CHANGE_NEW_OWNER:
+        if (s->clipboard_selfgrab_pending[selection]) {
+            s->clipboard_selfgrab_pending[selection] = FALSE;
+            break;
+        }
+        s->clipboard_by_guest[selection] = FALSE;
+        s->clip_hasdata[selection] = TRUE;
+        if (s->auto_clipboard_enable)
+            gtk_clipboard_request_targets(clipboard, clipboard_get_targets,
+                                          gtk_session);
+        break;
+    default:
+        s->clip_hasdata[selection] = FALSE;
+        break;
+    }
+}
+
+typedef struct
+{
+    GMainLoop *loop;
+    GtkSelectionData *selection_data;
+    guint info;
+    gulong timeout_handler;
+    guint selection;
+} RunInfo;
+
+static void clipboard_got_from_guest(SpiceMainChannel *main, guint selection,
+                                     guint type, guchar *data, guint size,
+                                     gpointer user_data)
+{
+    RunInfo *ri = user_data;
+
+    g_return_if_fail(selection == ri->selection);
+
+    SPICE_DEBUG("clipboard got data");
+
+    gtk_selection_data_set(ri->selection_data,
+        gdk_atom_intern_static_string(atom2agent[ri->info].xatom),
+        8, data, size);
+
+    if (g_main_loop_is_running (ri->loop))
+        g_main_loop_quit (ri->loop);
+}
+
+static gboolean clipboard_timeout(gpointer user_data)
+{
+    RunInfo *ri = user_data;
+
+    g_warning("clipboard get timed out");
+    if (g_main_loop_is_running (ri->loop))
+        g_main_loop_quit (ri->loop);
+
+    ri->timeout_handler = 0;
+    return FALSE;
+}
+
+static void clipboard_get(GtkClipboard *clipboard,
+                          GtkSelectionData *selection_data,
+                          guint info, gpointer user_data)
+{
+    RunInfo ri = { NULL, };
+    SpiceGtkSession *gtk_session = user_data;
+    SpiceGtkSessionPrivate *s = SPICE_GTK_SESSION_GET_PRIVATE(gtk_session);
+    gulong clipboard_handler;
+    int selection;
+
+    SPICE_DEBUG("clipboard get");
+
+    selection = get_selection_from_clipboard(s, clipboard);
+    g_return_if_fail(selection != -1);
+    g_return_if_fail(info < SPICE_N_ELEMENTS(atom2agent));
+    g_return_if_fail(s->main != NULL);
+
+    ri.selection_data = selection_data;
+    ri.info = info;
+    ri.loop = g_main_loop_new(NULL, FALSE);
+    ri.selection = selection;
+
+    clipboard_handler = g_signal_connect(s->main, "main-clipboard-selection",
+                                         G_CALLBACK(clipboard_got_from_guest),
+                                         &ri);
+    ri.timeout_handler = g_timeout_add_seconds(7, clipboard_timeout, &ri);
+    spice_main_clipboard_selection_request(s->main, selection,
+                                           atom2agent[info].vdagent);
+
+    /* apparently, this is needed to avoid dead-lock, from
+       gtk_dialog_run */
+    GDK_THREADS_LEAVE();
+    g_main_loop_run(ri.loop);
+    GDK_THREADS_ENTER();
+
+    g_main_loop_unref(ri.loop);
+    ri.loop = NULL;
+    g_signal_handler_disconnect(s->main, clipboard_handler);
+    if (ri.timeout_handler != 0)
+        g_source_remove(ri.timeout_handler);
+}
+
+static void clipboard_clear(GtkClipboard *clipboard, gpointer user_data)
+{
+    SPICE_DEBUG("clipboard_clear");
+    /* We watch for clipboard ownership changes and act on those, so we
+       don't need to do anything here */
+}
+
+static gboolean clipboard_grab(SpiceMainChannel *main, guint selection,
+                               guint32* types, guint32 ntypes,
+                               gpointer user_data)
+{
+    SpiceGtkSession *gtk_session = user_data;
+    SpiceGtkSessionPrivate *s = SPICE_GTK_SESSION_GET_PRIVATE(gtk_session);
+    GtkTargetEntry targets[SPICE_N_ELEMENTS(atom2agent)];
+    gboolean target_selected[SPICE_N_ELEMENTS(atom2agent)] = { FALSE, };
+    gboolean found;
+    GtkClipboard* cb;
+    int m, n, i;
+
+    cb = get_clipboard_from_selection(s, selection);
+    g_return_val_if_fail(cb != NULL, FALSE);
+
+    i = 0;
+    for (n = 0; n < ntypes; ++n) {
+        found = FALSE;
+        for (m = 0; m < SPICE_N_ELEMENTS(atom2agent); m++) {
+            if (atom2agent[m].vdagent == types[n] && !target_selected[m]) {
+                found = TRUE;
+                g_return_val_if_fail(i < SPICE_N_ELEMENTS(atom2agent), FALSE);
+                targets[i].target = (gchar*)atom2agent[m].xatom;
+                targets[i].flags = 0;
+                targets[i].info = m;
+                target_selected[m] = TRUE;
+                i += 1;
+            }
+        }
+        if (!found) {
+            g_warning("clipboard: couldn't find a matching type for: %d",
+                      types[n]);
+        }
+    }
+
+    g_free(s->clip_targets[selection]);
+    s->nclip_targets[selection] = i;
+    s->clip_targets[selection] = g_memdup(targets, sizeof(GtkTargetEntry) * i);
+    /* Receiving a grab implies we've released our own grab */
+    s->clip_grabbed[selection] = FALSE;
+
+    if (!s->auto_clipboard_enable || s->nclip_targets[selection] == 0)
+        goto skip_grab_clipboard;
+
+    if (!gtk_clipboard_set_with_data(cb, targets, i, clipboard_get,
+                                     clipboard_clear, gtk_session)) {
+        g_warning("clipboard grab failed");
+        return FALSE;
+    }
+    s->clipboard_selfgrab_pending[selection] = TRUE;
+    s->clipboard_by_guest[selection] = TRUE;
+    s->clip_hasdata[selection] = FALSE;
+
+skip_grab_clipboard:
+    return TRUE;
+}
+
+static void clipboard_received_cb(GtkClipboard *clipboard,
+                                  GtkSelectionData *selection_data,
+                                  gpointer user_data)
+{
+    SpiceGtkSession *gtk_session = user_data;
+    SpiceGtkSessionPrivate *s = SPICE_GTK_SESSION_GET_PRIVATE(gtk_session);
+    gint len = 0, m;
+    guint32 type = VD_AGENT_CLIPBOARD_NONE;
+    gchar* name;
+    GdkAtom atom;
+    int selection;
+
+    selection = get_selection_from_clipboard(s, clipboard);
+    g_return_if_fail(selection != -1);
+
+    len = gtk_selection_data_get_length(selection_data);
+    if (len == -1) {
+        SPICE_DEBUG("empty clipboard");
+        len = 0;
+    } else if (len == 0) {
+        SPICE_DEBUG("TODO: what should be done here?");
+    } else {
+        atom = gtk_selection_data_get_data_type(selection_data);
+        name = gdk_atom_name(atom);
+        for (m = 0; m < SPICE_N_ELEMENTS(atom2agent); m++) {
+            if (strcasecmp(name, atom2agent[m].xatom) == 0) {
+                break;
+            }
+        }
+
+        if (m >= SPICE_N_ELEMENTS(atom2agent)) {
+            g_warning("clipboard_received for unsupported type: %s", name);
+        } else {
+            type = atom2agent[m].vdagent;
+        }
+
+        g_free(name);
+    }
+
+    spice_main_clipboard_selection_notify(s->main, selection, type,
+        gtk_selection_data_get_data(selection_data), len);
+}
+
+static gboolean clipboard_request(SpiceMainChannel *main, guint selection,
+                                  guint type, gpointer user_data)
+{
+    SpiceGtkSession *gtk_session = user_data;
+    SpiceGtkSessionPrivate *s = SPICE_GTK_SESSION_GET_PRIVATE(gtk_session);
+    GdkAtom atom;
+    GtkClipboard* cb;
+    int m;
+
+    cb = get_clipboard_from_selection(s, selection);
+    g_return_val_if_fail(cb != NULL, FALSE);
+
+    for (m = 0; m < SPICE_N_ELEMENTS(atom2agent); m++) {
+        if (atom2agent[m].vdagent == type)
+            break;
+    }
+
+    g_return_val_if_fail(m < SPICE_N_ELEMENTS(atom2agent), FALSE);
+
+    atom = gdk_atom_intern_static_string(atom2agent[m].xatom);
+    gtk_clipboard_request_contents(cb, atom, clipboard_received_cb,
+                                   gtk_session);
+
+    return TRUE;
+}
+
+static void clipboard_release(SpiceMainChannel *main, guint selection,
+                              gpointer user_data)
+{
+    SpiceGtkSession *gtk_session = user_data;
+    SpiceGtkSessionPrivate *s = SPICE_GTK_SESSION_GET_PRIVATE(gtk_session);
+    GtkClipboard* clipboard = get_clipboard_from_selection(s, selection);
+    if (!clipboard)
+        return;
+
+    s->nclip_targets[selection] = 0;
+
+    if (!s->clipboard_by_guest[selection])
+        return;
+    gtk_clipboard_clear(clipboard);
+    s->clipboard_by_guest[selection] = FALSE;
+}
+
+static void channel_new(SpiceSession *session, SpiceChannel *channel,
+                        gpointer user_data)
+{
+    SpiceGtkSession *gtk_session = user_data;
+    SpiceGtkSessionPrivate *s = SPICE_GTK_SESSION_GET_PRIVATE(gtk_session);
+
+    if (SPICE_IS_MAIN_CHANNEL(channel)) {
+        s->main = SPICE_MAIN_CHANNEL(channel);
+        g_signal_connect(channel, "main-clipboard-selection-grab",
+                         G_CALLBACK(clipboard_grab), gtk_session);
+        g_signal_connect(channel, "main-clipboard-selection-request",
+                         G_CALLBACK(clipboard_request), gtk_session);
+        g_signal_connect(channel, "main-clipboard-selection-release",
+                         G_CALLBACK(clipboard_release), gtk_session);
+    }
+}
+
+static void channel_destroy(SpiceSession *session, SpiceChannel *channel,
+                            gpointer user_data)
+{
+    SpiceGtkSession *gtk_session = user_data;
+    SpiceGtkSessionPrivate *s = SPICE_GTK_SESSION_GET_PRIVATE(gtk_session);
+    guint i;
+
+    if (SPICE_IS_MAIN_CHANNEL(channel)) {
+        s->main = NULL;
+        for (i = 0; i < CLIPBOARD_LAST; ++i) {
+            if (s->clipboard_by_guest[i]) {
+                GtkClipboard *cb = get_clipboard_from_selection(s, i);
+                if (cb)
+                    gtk_clipboard_clear(cb);
+                s->clipboard_by_guest[i] = FALSE;
+            }
+            s->clip_grabbed[i] = FALSE;
+            s->nclip_targets[i] = 0;
+        }
+    }
+}
+
 /* ------------------------------------------------------------------ */
 /* public functions                                                   */
 
@@ -218,3 +750,49 @@ SpiceGtkSession *spice_gtk_session_get(SpiceSession *session)
 
     return SPICE_GTK_SESSION(gtk_session);
 }
+
+/**
+ * spice_gtk_session_copy_to_guest:
+ * @gtk_session:
+ *
+ * Copy client-side clipboard to guest clipboard.
+ **/
+void spice_gtk_session_copy_to_guest(SpiceGtkSession *gtk_session)
+{
+    SpiceGtkSessionPrivate *s = SPICE_GTK_SESSION_GET_PRIVATE(gtk_session);
+    int selection = VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD;
+
+    if (s->clip_hasdata[selection] && !s->clip_grabbed[selection]) {
+        gtk_clipboard_request_targets(s->clipboard, clipboard_get_targets,
+                                      gtk_session);
+    }
+}
+
+/**
+ * spice_gtk_session_paste_from_guest:
+ * @gtk_session:
+ *
+ * Copy guest clipboard to client-side clipboard.
+ **/
+void spice_gtk_session_paste_from_guest(SpiceGtkSession *gtk_session)
+{
+    SpiceGtkSessionPrivate *s = SPICE_GTK_SESSION_GET_PRIVATE(gtk_session);
+    int selection = VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD;
+
+    if (s->nclip_targets[selection] == 0) {
+        g_warning("Guest clipboard is not available.");
+        return;
+    }
+
+    if (!gtk_clipboard_set_with_data(s->clipboard,
+                                     s->clip_targets[selection],
+                                     s->nclip_targets[selection],
+                                     clipboard_get, clipboard_clear,
+                                     gtk_session)) {
+        g_warning("Clipboard grab failed");
+        return;
+    }
+    s->clipboard_selfgrab_pending[selection] = TRUE;
+    s->clipboard_by_guest[selection] = TRUE;
+    s->clip_hasdata[selection] = FALSE;
+}
diff --git a/gtk/spice-gtk-session.h b/gtk/spice-gtk-session.h
index 9c59fa2..a1da838 100644
--- a/gtk/spice-gtk-session.h
+++ b/gtk/spice-gtk-session.h
@@ -57,6 +57,8 @@ struct _SpiceGtkSessionClass
 GType spice_gtk_session_get_type(void);
 
 SpiceGtkSession *spice_gtk_session_get(SpiceSession *session);
+void spice_gtk_session_copy_to_guest(SpiceGtkSession *gtk_session);
+void spice_gtk_session_paste_from_guest(SpiceGtkSession *gtk_session);
 
 G_END_DECLS
 
diff --git a/gtk/spice-widget-priv.h b/gtk/spice-widget-priv.h
index bd6dedb..f94c8c6 100644
--- a/gtk/spice-widget-priv.h
+++ b/gtk/spice-widget-priv.h
@@ -36,13 +36,11 @@ G_BEGIN_DECLS
 
 #include "spice-widget.h"
 #include "spice-common.h"
-#include <spice/vd_agent.h>
+#include "spice-gtk-session.h"
 
 #define SPICE_DISPLAY_GET_PRIVATE(obj)                                  \
     (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_DISPLAY, SpiceDisplayPrivate))
 
-#define CLIPBOARD_LAST (VD_AGENT_CLIPBOARD_SELECTION_SECONDARY + 1)
-
 struct _SpiceDisplayPrivate {
     gint                    channel_id;
 
@@ -50,7 +48,6 @@ struct _SpiceDisplayPrivate {
     bool                    keyboard_grab_enable;
     bool                    mouse_grab_enable;
     bool                    resize_guest_enable;
-    bool                    auto_clipboard_enable;
     bool                    auto_usbredir_enable;
 
     /* state */
@@ -77,16 +74,8 @@ struct _SpiceDisplayPrivate {
     cairo_surface_t         *ximage;
 #endif
 
-    GtkClipboard            *clipboard;
-    GtkClipboard            *clipboard_primary;
-    GtkTargetEntry          *clip_targets[CLIPBOARD_LAST];
-    guint                   nclip_targets[CLIPBOARD_LAST];
-    bool                    clip_hasdata[CLIPBOARD_LAST];
-    bool                    clip_grabbed[CLIPBOARD_LAST];
-    gboolean                clipboard_by_guest[CLIPBOARD_LAST];
-    gboolean                clipboard_selfgrab_pending[CLIPBOARD_LAST];
-
     SpiceSession            *session;
+    SpiceGtkSession         *gtk_session;
     SpiceMainChannel        *main;
     SpiceChannel            *display;
     SpiceCursorChannel      *cursor;
diff --git a/gtk/spice-widget.c b/gtk/spice-widget.c
index 2beea73..8b1c9ad 100644
--- a/gtk/spice-widget.c
+++ b/gtk/spice-widget.c
@@ -110,8 +110,6 @@ static void try_keyboard_ungrab(SpiceDisplay *display);
 static void try_mouse_grab(GtkWidget *widget);
 static void try_mouse_ungrab(GtkWidget *widget);
 static void recalc_geometry(GtkWidget *widget, gboolean set_display);
-static void clipboard_owner_change(GtkClipboard *clipboard,
-                                   GdkEventOwnerChange *event, gpointer user_data);
 static void disconnect_main(SpiceDisplay *display);
 static void disconnect_cursor(SpiceDisplay *display);
 static void disconnect_display(SpiceDisplay *display);
@@ -129,6 +127,7 @@ static void spice_display_get_property(GObject    *object,
 {
     SpiceDisplay *display = SPICE_DISPLAY(object);
     SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(display);
+    gboolean boolean;
 
     switch (prop_id) {
     case PROP_KEYBOARD_GRAB:
@@ -141,7 +140,8 @@ static void spice_display_get_property(GObject    *object,
         g_value_set_boolean(value, d->resize_guest_enable);
         break;
     case PROP_AUTO_CLIPBOARD:
-        g_value_set_boolean(value, d->auto_clipboard_enable);
+        g_object_get(d->gtk_session, "auto-clipboard", &boolean, NULL);
+        g_value_set_boolean(value, boolean);
         break;
     case PROP_AUTO_USBREDIR:
         g_value_set_boolean(value, d->auto_usbredir_enable);
@@ -198,7 +198,8 @@ static void spice_display_set_property(GObject      *object,
         }
         break;
     case PROP_AUTO_CLIPBOARD:
-        d->auto_clipboard_enable = g_value_get_boolean(value);
+        g_object_set(d->gtk_session, "auto-clipboard",
+                     g_value_get_boolean(value), NULL);
         break;
     case PROP_AUTO_USBREDIR:
         d->auto_usbredir_enable = g_value_get_boolean(value);
@@ -221,17 +222,6 @@ static void spice_display_dispose(GObject *obj)
     disconnect_display(display);
     disconnect_cursor(display);
 
-    if (d->clipboard) {
-        g_signal_handlers_disconnect_by_func(d->clipboard, G_CALLBACK(clipboard_owner_change),
-                                             display);
-        d->clipboard = NULL;
-    }
-
-    if (d->clipboard_primary) {
-        g_signal_handlers_disconnect_by_func(d->clipboard_primary, G_CALLBACK(clipboard_owner_change),
-                                             display);
-        d->clipboard_primary = NULL;
-    }
     if (d->session) {
         g_signal_handlers_disconnect_by_func(d->session, G_CALLBACK(channel_new),
                                              display);
@@ -239,6 +229,7 @@ static void spice_display_dispose(GObject *obj)
                                              display);
         g_object_unref(d->session);
         d->session = NULL;
+        d->gtk_session = NULL;
     }
 }
 
@@ -246,7 +237,6 @@ static void spice_display_finalize(GObject *obj)
 {
     SpiceDisplay *display = SPICE_DISPLAY(obj);
     SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(display);
-    int i;
 
     SPICE_DEBUG("Finalize spice display");
 
@@ -257,11 +247,6 @@ static void spice_display_finalize(GObject *obj)
     g_free(d->activeseq);
     d->activeseq = NULL;
 
-    for (i = 0; i < CLIPBOARD_LAST; ++i) {
-        g_free(d->clip_targets[i]);
-        d->clip_targets[i] = NULL;
-    }
-
     G_OBJECT_CLASS(spice_display_parent_class)->finalize(obj);
 }
 
@@ -289,13 +274,6 @@ static void spice_display_init(SpiceDisplay *display)
     d->grabseq = spice_grab_sequence_new_from_string("Control_L+Alt_L");
     d->activeseq = g_new0(gboolean, d->grabseq->nkeysyms);
 
-    d->clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
-    g_signal_connect(G_OBJECT(d->clipboard), "owner-change",
-                     G_CALLBACK(clipboard_owner_change), display);
-    d->clipboard_primary = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
-    g_signal_connect(G_OBJECT(d->clipboard_primary), "owner-change",
-                     G_CALLBACK(clipboard_owner_change), display);
-
     if (g_getenv("SPICE_DEBUG_CURSOR"))
         d->mouse_cursor = gdk_cursor_new(GDK_DOT);
     else
@@ -1088,174 +1066,6 @@ static gboolean configure_event(GtkWidget *widget, GdkEventConfigure *conf)
 
 /* ---------------------------------------------------------------- */
 
-static GtkClipboard* get_clipboard_from_selection(SpiceDisplayPrivate *d, guint selection)
-{
-    if (selection == VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD) {
-        return d->clipboard;
-    } else if (selection == VD_AGENT_CLIPBOARD_SELECTION_PRIMARY) {
-        return d->clipboard_primary;
-    } else {
-        g_warning("Unhandled clipboard selection: %d", selection);
-        return NULL;
-    }
-}
-
-static gint get_selection_from_clipboard(SpiceDisplayPrivate *d, GtkClipboard* cb)
-{
-    if (cb == d->clipboard) {
-        return VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD;
-    } else if (cb == d->clipboard_primary) {
-        return VD_AGENT_CLIPBOARD_SELECTION_PRIMARY;
-    } else {
-        g_warning("Unhandled clipboard");
-        return -1;
-    }
-}
-
-static const struct {
-    const char  *xatom;
-    uint32_t    vdagent;
-    uint32_t    flags;
-} atom2agent[] = {
-    {
-        .vdagent = VD_AGENT_CLIPBOARD_UTF8_TEXT,
-        .xatom   = "UTF8_STRING",
-    },{
-        .vdagent = VD_AGENT_CLIPBOARD_UTF8_TEXT,
-        .xatom   = "text/plain;charset=utf-8"
-    },{
-        .vdagent = VD_AGENT_CLIPBOARD_UTF8_TEXT,
-        .xatom   = "STRING"
-    },{
-        .vdagent = VD_AGENT_CLIPBOARD_UTF8_TEXT,
-        .xatom   = "TEXT"
-    },{
-        .vdagent = VD_AGENT_CLIPBOARD_UTF8_TEXT,
-        .xatom   = "text/plain"
-    },{
-        .vdagent = VD_AGENT_CLIPBOARD_IMAGE_PNG,
-        .xatom   = "image/png"
-    },{
-        .vdagent = VD_AGENT_CLIPBOARD_IMAGE_BMP,
-        .xatom   = "image/bmp"
-    },{
-        .vdagent = VD_AGENT_CLIPBOARD_IMAGE_BMP,
-        .xatom   = "image/x-bmp"
-    },{
-        .vdagent = VD_AGENT_CLIPBOARD_IMAGE_BMP,
-        .xatom   = "image/x-MS-bmp"
-    },{
-        .vdagent = VD_AGENT_CLIPBOARD_IMAGE_BMP,
-        .xatom   = "image/x-win-bitmap"
-    },{
-        .vdagent = VD_AGENT_CLIPBOARD_IMAGE_TIFF,
-        .xatom   = "image/tiff"
-    },{
-        .vdagent = VD_AGENT_CLIPBOARD_IMAGE_JPG,
-        .xatom   = "image/jpeg"
-    }
-};
-
-static void clipboard_get_targets(GtkClipboard *clipboard,
-                                  GdkAtom *atoms,
-                                  gint n_atoms,
-                                  gpointer data)
-{
-    SpiceDisplay *display = data;
-    SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(display);
-    guint32 types[SPICE_N_ELEMENTS(atom2agent)];
-    char *name;
-    int a, m, t;
-    int selection;
-
-    selection = get_selection_from_clipboard(d, clipboard);
-    g_return_if_fail(selection != -1);
-
-    SPICE_DEBUG("%s:", __FUNCTION__);
-    if (spice_util_get_debug()) {
-        for (a = 0; a < n_atoms; a++) {
-            name = gdk_atom_name(atoms[a]);
-            SPICE_DEBUG(" \"%s\"", name);
-            g_free(name);
-        }
-    }
-
-    memset(types, 0, sizeof(types));
-    for (a = 0; a < n_atoms; a++) {
-        name = gdk_atom_name(atoms[a]);
-        for (m = 0; m < SPICE_N_ELEMENTS(atom2agent); m++) {
-            if (strcasecmp(name, atom2agent[m].xatom) != 0) {
-                continue;
-            }
-            /* found match */
-            for (t = 0; t < SPICE_N_ELEMENTS(atom2agent); t++) {
-                if (types[t] == atom2agent[m].vdagent) {
-                    /* type already in list */
-                    break;
-                }
-                if (types[t] == 0) {
-                    /* add type to empty slot */
-                    types[t] = atom2agent[m].vdagent;
-                    break;
-                }
-            }
-            break;
-        }
-        g_free(name);
-    }
-    for (t = 0; t < SPICE_N_ELEMENTS(atom2agent); t++) {
-        if (types[t] == 0) {
-            break;
-        }
-    }
-    if (!d->clip_grabbed[selection] && t > 0) {
-        d->clip_grabbed[selection] = TRUE;
-        spice_main_clipboard_selection_grab(d->main,
-            get_selection_from_clipboard(d, clipboard), types, t);
-        /* Sending a grab causes the agent to do an impicit release */
-        d->nclip_targets[selection] = 0;
-    }
-}
-
-static void clipboard_owner_change(GtkClipboard        *clipboard,
-                                   GdkEventOwnerChange *event,
-                                   gpointer            data)
-{
-    SpiceDisplay *display = data;
-    SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(display);
-    int selection;
-
-    selection = get_selection_from_clipboard(d, clipboard);
-    g_return_if_fail(selection != -1);
-
-    if (d->main == NULL)
-        return;
-
-    if (d->clip_grabbed[selection]) {
-        d->clip_grabbed[selection] = FALSE;
-        spice_main_clipboard_selection_release(d->main,
-            get_selection_from_clipboard(d, clipboard));
-    }
-
-    switch (event->reason) {
-    case GDK_OWNER_CHANGE_NEW_OWNER:
-        if (d->clipboard_selfgrab_pending[selection]) {
-            d->clipboard_selfgrab_pending[selection] = FALSE;
-            break;
-        }
-        d->clipboard_by_guest[selection] = FALSE;
-        d->clip_hasdata[selection] = TRUE;
-        if (d->auto_clipboard_enable)
-            gtk_clipboard_request_targets(clipboard, clipboard_get_targets, data);
-        break;
-    default:
-        d->clip_hasdata[selection] = FALSE;
-        break;
-    }
-}
-
-/* ---------------------------------------------------------------- */
-
 static void spice_display_class_init(SpiceDisplayClass *klass)
 {
     GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
@@ -1314,6 +1124,12 @@ static void spice_display_class_init(SpiceDisplayClass *klass)
                               G_PARAM_CONSTRUCT |
                               G_PARAM_STATIC_STRINGS));
 
+    /**
+     * SpiceDisplay:auto-clipboard:
+     *
+     * When this is true the clipboard gets automatically shared between host
+     * and guest.
+     **/
     g_object_class_install_property
         (gobject_class, PROP_AUTO_CLIPBOARD,
          g_param_spec_boolean("auto-clipboard",
@@ -1593,18 +1409,12 @@ static void cursor_reset(SpiceCursorChannel *channel, gpointer data)
 static void disconnect_main(SpiceDisplay *display)
 {
     SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(display);
-    gint i;
 
     if (d->main == NULL)
         return;
     g_signal_handlers_disconnect_by_func(d->main, G_CALLBACK(mouse_update),
                                          display);
     d->main = NULL;
-    for (i = 0; i < CLIPBOARD_LAST; ++i) {
-        d->clipboard_by_guest[i] = FALSE;
-        d->clip_grabbed[i] = FALSE;
-        d->nclip_targets[i] = 0;
-    }
 }
 
 static void disconnect_display(SpiceDisplay *display)
@@ -1639,219 +1449,6 @@ static void disconnect_cursor(SpiceDisplay *display)
     d->cursor = NULL;
 }
 
-typedef struct
-{
-    GMainLoop *loop;
-    SpiceDisplay *display;
-    GtkSelectionData *selection_data;
-    guint info;
-    gulong timeout_handler;
-    guint selection;
-} RunInfo;
-
-static void clipboard_got_from_guest(SpiceMainChannel *main, guint selection,
-                                     guint type, guchar *data, guint size,
-                                     gpointer userdata)
-{
-    RunInfo *ri = userdata;
-
-    g_return_if_fail(selection == ri->selection);
-
-    SPICE_DEBUG("clipboard got data");
-
-    gtk_selection_data_set(ri->selection_data,
-        gdk_atom_intern_static_string(atom2agent[ri->info].xatom),
-        8, data, size);
-
-    if (g_main_loop_is_running (ri->loop))
-        g_main_loop_quit (ri->loop);
-}
-
-static gboolean clipboard_timeout(gpointer data)
-{
-    RunInfo *ri = data;
-
-    g_warning("clipboard get timed out");
-    if (g_main_loop_is_running (ri->loop))
-        g_main_loop_quit (ri->loop);
-
-    ri->timeout_handler = 0;
-    return FALSE;
-}
-
-static void clipboard_get(GtkClipboard *clipboard, GtkSelectionData *selection_data,
-                          guint info, gpointer display)
-{
-    RunInfo ri = { NULL, };
-    SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(display);
-    gulong clipboard_handler;
-
-    SPICE_DEBUG("clipboard get");
-
-    g_return_if_fail(info < SPICE_N_ELEMENTS(atom2agent));
-    g_return_if_fail(get_selection_from_clipboard(d, clipboard) != -1);
-
-    ri.display = display;
-    ri.selection_data = selection_data;
-    ri.info = info;
-    ri.loop = g_main_loop_new(NULL, FALSE);
-    ri.selection = get_selection_from_clipboard(d, clipboard);
-
-    clipboard_handler = g_signal_connect(d->main, "main-clipboard-selection",
-                                         G_CALLBACK(clipboard_got_from_guest), &ri);
-    ri.timeout_handler = g_timeout_add_seconds(7, clipboard_timeout, &ri);
-    spice_main_clipboard_selection_request(d->main, ri.selection, atom2agent[info].vdagent);
-
-    /* apparently, this is needed to avoid dead-lock, from
-       gtk_dialog_run */
-    GDK_THREADS_LEAVE();
-    g_main_loop_run(ri.loop);
-    GDK_THREADS_ENTER();
-
-    g_main_loop_unref(ri.loop);
-    ri.loop = NULL;
-    g_signal_handler_disconnect(d->main, clipboard_handler);
-    if (ri.timeout_handler != 0)
-        g_source_remove(ri.timeout_handler);
-}
-
-static void clipboard_clear(GtkClipboard *clipboard, gpointer display)
-{
-    SPICE_DEBUG("clipboard_clear");
-    /* We watch for clipboard ownership changes and act on those, so we
-       don't need to do anything here */
-}
-
-static gboolean clipboard_grab(SpiceMainChannel *main, guint selection,
-                               guint32* types, guint32 ntypes, gpointer display)
-{
-    int m, n, i;
-    SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(display);
-    GtkTargetEntry targets[SPICE_N_ELEMENTS(atom2agent)];
-    gboolean target_selected[SPICE_N_ELEMENTS(atom2agent)] = { FALSE, };
-    gboolean found;
-    GtkClipboard* cb;
-
-    cb = get_clipboard_from_selection(d, selection);
-    g_return_val_if_fail(cb != NULL, FALSE);
-
-    i = 0;
-    for (n = 0; n < ntypes; ++n) {
-        found = FALSE;
-        for (m = 0; m < SPICE_N_ELEMENTS(atom2agent); m++) {
-            if (atom2agent[m].vdagent == types[n] && !target_selected[m]) {
-                found = TRUE;
-                g_return_val_if_fail(i < SPICE_N_ELEMENTS(atom2agent), FALSE);
-                targets[i].target = (gchar*)atom2agent[m].xatom;
-                targets[i].flags = 0;
-                targets[i].info = m;
-                target_selected[m] = TRUE;
-                i += 1;
-            }
-        }
-        if (!found) {
-            g_warning("clipboard: couldn't find a matching type for: %d", types[n]);
-        }
-    }
-
-    g_free(d->clip_targets[selection]);
-    d->nclip_targets[selection] = i;
-    d->clip_targets[selection] = g_memdup(targets, sizeof(GtkTargetEntry) * i);
-    /* Receiving a grab implies we've released our own grab */
-    d->clip_grabbed[selection] = FALSE;
-
-    if (!d->auto_clipboard_enable || d->nclip_targets[selection] == 0)
-        goto skip_grab_clipboard;
-
-    if (!gtk_clipboard_set_with_data(cb, targets, i,
-                                     clipboard_get, clipboard_clear, display)) {
-        g_warning("clipboard grab failed");
-        return FALSE;
-    }
-    d->clipboard_selfgrab_pending[selection] = TRUE;
-    d->clipboard_by_guest[selection] = TRUE;
-    d->clip_hasdata[selection] = FALSE;
-
-skip_grab_clipboard:
-    return TRUE;
-}
-
-static void clipboard_received_cb(GtkClipboard *clipboard,
-                                  GtkSelectionData *selection_data,
-                                  gpointer display)
-{
-    SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(display);
-    gint len = 0, m;
-    guint32 type = VD_AGENT_CLIPBOARD_NONE;
-    gchar* name;
-    GdkAtom atom;
-
-    g_return_if_fail(get_selection_from_clipboard(d, clipboard) != -1);
-
-    len = gtk_selection_data_get_length(selection_data);
-    if (len == -1) {
-        SPICE_DEBUG("empty clipboard");
-        len = 0;
-    } else if (len == 0) {
-        SPICE_DEBUG("TODO: what should be done here?");
-    } else {
-        atom = gtk_selection_data_get_data_type(selection_data);
-        name = gdk_atom_name(atom);
-        for (m = 0; m < SPICE_N_ELEMENTS(atom2agent); m++) {
-            if (strcasecmp(name, atom2agent[m].xatom) == 0) {
-                break;
-            }
-        }
-
-        if (m >= SPICE_N_ELEMENTS(atom2agent)) {
-            g_warning("clipboard_received for unsupported type: %s", name);
-        } else {
-            type = atom2agent[m].vdagent;
-        }
-
-        g_free(name);
-    }
-
-    spice_main_clipboard_selection_notify(d->main, get_selection_from_clipboard(d, clipboard),
-        type, gtk_selection_data_get_data(selection_data), len);
-}
-
-static gboolean clipboard_request(SpiceMainChannel *main, guint selection,
-                                  guint type, gpointer display)
-{
-    SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(display);
-    int m;
-    GdkAtom atom;
-
-    for (m = 0; m < SPICE_N_ELEMENTS(atom2agent); m++) {
-        if (atom2agent[m].vdagent == type)
-            break;
-    }
-
-    g_return_val_if_fail(m < SPICE_N_ELEMENTS(atom2agent), FALSE);
-
-    atom = gdk_atom_intern_static_string(atom2agent[m].xatom);
-    gtk_clipboard_request_contents(get_clipboard_from_selection(d, selection), atom,
-                                   clipboard_received_cb, display);
-
-    return TRUE;
-}
-
-static void clipboard_release(SpiceMainChannel *main, guint selection, gpointer data)
-{
-    SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(data);
-    GtkClipboard* clipboard = get_clipboard_from_selection(d, selection);
-    if (!clipboard)
-        return;
-
-    d->nclip_targets[selection] = 0;
-
-    if (!d->clipboard_by_guest[selection])
-        return;
-    gtk_clipboard_clear(clipboard);
-    d->clipboard_by_guest[selection] = FALSE;
-}
-
 static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data)
 {
     SpiceDisplay *display = data;
@@ -1864,14 +1461,6 @@ static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data)
         g_signal_connect(channel, "main-mouse-update",
                          G_CALLBACK(mouse_update), display);
         mouse_update(channel, display);
-        if (id != d->channel_id)
-            return;
-        g_signal_connect(channel, "main-clipboard-selection-grab",
-                         G_CALLBACK(clipboard_grab), display);
-        g_signal_connect(channel, "main-clipboard-selection-request",
-                         G_CALLBACK(clipboard_request), display);
-        g_signal_connect(channel, "main-clipboard-selection-release",
-                         G_CALLBACK(clipboard_release), display);
         return;
     }
 
@@ -2008,6 +1597,7 @@ SpiceDisplay *spice_display_new(SpiceSession *session, int id)
     display = g_object_new(SPICE_TYPE_DISPLAY, NULL);
     d = SPICE_DISPLAY_GET_PRIVATE(display);
     d->session = g_object_ref(session);
+    d->gtk_session = spice_gtk_session_get(d->session);
     d->channel_id = id;
 
     g_signal_connect(session, "channel-new",
@@ -2043,32 +1633,25 @@ void spice_display_mouse_ungrab(SpiceDisplay *display)
 void spice_display_copy_to_guest(SpiceDisplay *display)
 {
     SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(display);
-    int selection = VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD;
 
-    if (d->clip_hasdata[selection] && !d->clip_grabbed[selection]) {
-        gtk_clipboard_request_targets(d->clipboard, clipboard_get_targets, display);
-    }
+    g_return_if_fail(d->gtk_session != NULL);
+
+    spice_gtk_session_copy_to_guest(d->gtk_session);
 }
 
+/**
+ * spice_display_paste_from_guest:
+ * @display:
+ *
+ * Copy guest clipboard to client-side clipboard.
+ **/
 void spice_display_paste_from_guest(SpiceDisplay *display)
 {
     SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(display);
-    int selection = VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD;
 
-    if (d->nclip_targets[selection] == 0) {
-        g_warning("Guest clipboard is not available.");
-        return;
-    }
+    g_return_if_fail(d->gtk_session != NULL);
 
-    if (!gtk_clipboard_set_with_data(d->clipboard,
-                                     d->clip_targets[selection], d->nclip_targets[selection],
-                                     clipboard_get, clipboard_clear, display)) {
-        g_warning("Clipboard grab failed");
-        return;
-    }
-    d->clipboard_selfgrab_pending[selection] = TRUE;
-    d->clipboard_by_guest[selection] = TRUE;
-    d->clip_hasdata[selection] = FALSE;
+    spice_gtk_session_paste_from_guest(d->gtk_session);
 }
 
 /**
-- 
1.7.6.4



More information about the Spice-devel mailing list