[Spice-commits] 5 commits - doc/reference gtk/Makefile.am gtk/map-file gtk/spice-channel-cache.h gtk/spice-gtk-session.c gtk/spice-gtk-session.h gtk/spice-session-priv.h gtk/spice-widget-priv.h gtk/spice-widget.c gtk/spicy.c gtk/usb-device-manager.c gtk/usb-device-manager.h

Hans de Goede jwrdegoede at kemper.freedesktop.org
Fri Oct 7 02:00:23 PDT 2011


 doc/reference/spice-gtk-docs.xml     |    1 
 doc/reference/spice-gtk-sections.txt |   22 
 doc/reference/spice-gtk.types        |    2 
 gtk/Makefile.am                      |    3 
 gtk/map-file                         |    6 
 gtk/spice-channel-cache.h            |    2 
 gtk/spice-gtk-session.c              |  809 +++++++++++++++++++++++++++++++++++
 gtk/spice-gtk-session.h              |   65 ++
 gtk/spice-session-priv.h             |    1 
 gtk/spice-widget-priv.h              |   15 
 gtk/spice-widget.c                   |  595 ++++---------------------
 gtk/spicy.c                          |   19 
 gtk/usb-device-manager.c             |  180 ++++---
 gtk/usb-device-manager.h             |    8 
 14 files changed, 1141 insertions(+), 587 deletions(-)

New commits:
commit 971eb9be73ee306e7e5c1a0d07251fd01f967d1d
Author: Hans de Goede <hdegoede at redhat.com>
Date:   Thu Oct 6 17:06:06 2011 +0200

    usb-device-manager: One instance per session instead of a singleton
    
    Since usb device manager keeps track of which usb channels there are and
    if they have usb devices attached there should be one usb-device-manager
    instance per session, rather then one global singleton.
    
    Tying the usb-device-manager to the session also allows us to get rid of
    spice_usb_device_manager_[un]register_channel and the need for SpiceDisplay
    to call these.
    
    Signed-off-by: Hans de Goede <hdegoede at redhat.com>

diff --git a/doc/reference/spice-gtk-sections.txt b/doc/reference/spice-gtk-sections.txt
index 56ae829..870352d 100644
--- a/doc/reference/spice-gtk-sections.txt
+++ b/doc/reference/spice-gtk-sections.txt
@@ -265,8 +265,6 @@ SpiceUsbDeviceManager
 SpiceUsbDeviceManagerClass
 <SUBSECTION>
 spice_usb_device_manager_get
-spice_usb_device_manager_register_channel
-spice_usb_device_manager_unregister_channel
 spice_usb_device_manager_get_devices
 spice_usb_device_manager_is_device_connected
 spice_usb_device_manager_connect_device
diff --git a/gtk/map-file b/gtk/map-file
index 82440c0..0b1b26f 100644
--- a/gtk/map-file
+++ b/gtk/map-file
@@ -84,8 +84,6 @@ spice_usb_device_get_type;
 spice_usb_device_get_description;
 spice_usb_device_manager_get_type;
 spice_usb_device_manager_get;
-spice_usb_device_manager_register_channel;
-spice_usb_device_manager_unregister_channel;
 spice_usb_device_manager_get_devices;
 spice_usb_device_manager_is_device_connected;
 spice_usb_device_manager_connect_device;
diff --git a/gtk/spice-widget.c b/gtk/spice-widget.c
index 2ee9855..5b53caf 100644
--- a/gtk/spice-widget.c
+++ b/gtk/spice-widget.c
@@ -619,7 +619,7 @@ static void update_auto_usbredir(SpiceDisplay *display)
         auto_connect = TRUE;
 
     /* FIXME: allow specifying a different GMainContext then the default */
-    manager = spice_usb_device_manager_get(NULL /* FIXME */, NULL);
+    manager = spice_usb_device_manager_get(d->session, NULL /* FIXME */, NULL);
     if (manager) {
         g_object_set(manager, "auto-connect", auto_connect, NULL);
     }
@@ -1592,18 +1592,6 @@ static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data)
     }
 #endif
 
-    if (SPICE_IS_USBREDIR_CHANNEL(channel)) {
-        SpiceUsbDeviceManager *manager;
-
-        /* FIXME: allow specifying a different GMainContext then the default */
-        manager = spice_usb_device_manager_get(NULL /* FIXME */, NULL);
-        if (manager) {
-            spice_usb_device_manager_register_channel(manager,
-                                              SPICE_USBREDIR_CHANNEL(channel));
-        }
-        return;
-    }
-
     return;
 }
 
@@ -1647,17 +1635,6 @@ static void channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer dat
     }
 #endif
 
-    if (SPICE_IS_USBREDIR_CHANNEL(channel)) {
-        SpiceUsbDeviceManager *manager;
-
-        manager = spice_usb_device_manager_get(NULL /* FIXME */, NULL);
-        if (manager) {
-            spice_usb_device_manager_unregister_channel(manager,
-                                              SPICE_USBREDIR_CHANNEL(channel));
-        }
-        return;
-    }
-
     return;
 }
 
diff --git a/gtk/spicy.c b/gtk/spicy.c
index a118ecd..77f339c 100644
--- a/gtk/spicy.c
+++ b/gtk/spicy.c
@@ -96,6 +96,10 @@ static void connection_disconnect(spice_connection *conn);
 static void connection_destroy(spice_connection *conn);
 static void resolution_fullscreen(struct spice_window *win);
 static void resolution_restore(struct spice_window *win);
+static void auto_connect_failed(SpiceUsbDeviceManager *manager,
+                                SpiceUsbDevice        *device,
+                                GError                *error,
+                                gpointer               data);
 
 /* ------------------------------------------------------------------ */
 
@@ -1438,6 +1442,7 @@ static void migration_state(GObject *session,
 static spice_connection *connection_new(void)
 {
     spice_connection *conn;
+    SpiceUsbDeviceManager *manager;
 
     conn = g_new0(spice_connection, 1);
     conn->session = spice_session_new();
@@ -1447,6 +1452,13 @@ static spice_connection *connection_new(void)
                      G_CALLBACK(channel_destroy), conn);
     g_signal_connect(conn->session, "notify::migration-state",
                      G_CALLBACK(migration_state), conn);
+
+    manager = spice_usb_device_manager_get(conn->session, NULL, NULL);
+    if (manager) {
+        g_signal_connect(manager, "auto-connect-failed",
+                         G_CALLBACK(auto_connect_failed), NULL);
+    }
+
     connections++;
     SPICE_DEBUG("%s (%d)", __FUNCTION__, connections);
     return conn;
@@ -1555,7 +1567,6 @@ int main(int argc, char *argv[])
     GOptionContext *context;
     spice_connection *conn;
     gchar *conf_file, *conf;
-    SpiceUsbDeviceManager *manager;
 
     g_thread_init(NULL);
     bindtextdomain(GETTEXT_PACKAGE, SPICE_GTK_LOCALEDIR);
@@ -1616,12 +1627,6 @@ int main(int argc, char *argv[])
         g_signal_connect(rrscreen, "changed", G_CALLBACK(on_screen_changed), NULL);
     on_screen_changed(rrscreen, NULL);
 
-    manager = spice_usb_device_manager_get(NULL, NULL);
-    if (manager) {
-        g_signal_connect(manager, "auto-connect-failed",
-                         G_CALLBACK(auto_connect_failed), NULL);
-    }
-
     conn = connection_new();
     spice_set_session_option(conn->session);
     spice_cmdline_session_setup(conn->session);
diff --git a/gtk/usb-device-manager.c b/gtk/usb-device-manager.c
index f2dbf90..929e636 100644
--- a/gtk/usb-device-manager.c
+++ b/gtk/usb-device-manager.c
@@ -48,13 +48,29 @@
  * devices plugging/unplugging. If #SpiceUsbDeviceManager:auto-connect
  * is set to %TRUE, it will automatically connect newly plugged USB
  * devices to available channels.
+ *
+ * There should always be a 1:1 relation between #SpiceUsbDeviceManager objects
+ * and #SpiceSession objects. Therefor there is no
+ * spice_usb_device_manager_new, instead there is
+ * spice_usb_device_manager_get() which ensures this 1:1 relation.
  */
 
+/* ------------------------------------------------------------------ */
+/* Prototypes for private functions */
+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_USB_DEVICE_MANAGER_GET_PRIVATE(obj)                                  \
     (G_TYPE_INSTANCE_GET_PRIVATE ((obj), SPICE_TYPE_USB_DEVICE_MANAGER, SpiceUsbDeviceManagerPrivate))
 
 enum {
     PROP_0,
+    PROP_SESSION,
     PROP_MAIN_CONTEXT,
     PROP_AUTO_CONNECT,
 };
@@ -68,6 +84,7 @@ enum
 };
 
 struct _SpiceUsbDeviceManagerPrivate {
+    SpiceSession *session;
     GMainContext *main_context;
     gboolean auto_connect;
 #ifdef USE_USBREDIR
@@ -120,10 +137,13 @@ static gboolean spice_usb_device_manager_initable_init(GInitable  *initable,
                                                     GCancellable  *cancellable,
                                                     GError        **err)
 {
-#ifdef USE_USBREDIR
-    GError *my_err = NULL;
+    GList *list;
+    GList *it;
     SpiceUsbDeviceManager *self;
     SpiceUsbDeviceManagerPrivate *priv;
+#ifdef USE_USBREDIR
+    GError *my_err = NULL;
+#endif
 
     g_return_val_if_fail(SPICE_IS_USB_DEVICE_MANAGER(initable), FALSE);
     g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
@@ -131,11 +151,29 @@ static gboolean spice_usb_device_manager_initable_init(GInitable  *initable,
     if (cancellable != NULL) {
         g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
                             "Cancellable initialization not supported");
+        return FALSE;
     }
 
     self = SPICE_USB_DEVICE_MANAGER(initable);
     priv = self->priv;
 
+    if (!priv->session) {
+        g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                "SpiceUsbDeviceManager constructed without a session");
+        return FALSE;
+    }
+
+    g_signal_connect(priv->session, "channel-new",
+                     G_CALLBACK(channel_new), self);
+    g_signal_connect(priv->session, "channel-destroy",
+                     G_CALLBACK(channel_destroy), self);
+    list = spice_session_get_channels(priv->session);
+    for (it = g_list_first(list); it != NULL; it = g_list_next(it)) {
+        channel_new(priv->session, it->data, (gpointer*)self);
+    }
+    g_list_free(list);
+
+#ifdef USE_USBREDIR
     priv->context = g_usb_context_new(&my_err);
     if (priv->context == NULL) {
         g_warning("Could not get a GUsbContext, disabling USB support: %s",
@@ -200,6 +238,9 @@ static void spice_usb_device_manager_get_property(GObject     *gobject,
     SpiceUsbDeviceManagerPrivate *priv = self->priv;
 
     switch (prop_id) {
+    case PROP_SESSION:
+        g_value_set_object(value, priv->session);
+        break;
     case PROP_MAIN_CONTEXT:
         g_value_set_pointer(value, priv->main_context);
         break;
@@ -221,6 +262,9 @@ static void spice_usb_device_manager_set_property(GObject       *gobject,
     SpiceUsbDeviceManagerPrivate *priv = self->priv;
 
     switch (prop_id) {
+    case PROP_SESSION:
+        priv->session = g_value_get_object(value);
+        break;
     case PROP_MAIN_CONTEXT:
         priv->main_context = g_value_get_pointer(value);
         break;
@@ -243,6 +287,21 @@ static void spice_usb_device_manager_class_init(SpiceUsbDeviceManagerClass *klas
     gobject_class->set_property = spice_usb_device_manager_set_property;
 
     /**
+     * SpiceUsbDeviceManager:session:
+     *
+     * #SpiceSession this #SpiceUsbDeviceManager is associated with
+     *
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_SESSION,
+         g_param_spec_object("session",
+                             "Session",
+                             "SpiceSession",
+                             SPICE_TYPE_SESSION,
+                             G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+                             G_PARAM_STATIC_STRINGS));
+
+    /**
      * SpiceUsbDeviceManager:main-context:
      */
     pspec = g_param_spec_pointer("main-context", "Main Context",
@@ -326,6 +385,24 @@ static void spice_usb_device_manager_class_init(SpiceUsbDeviceManagerClass *klas
 /* ------------------------------------------------------------------ */
 /* callbacks                                                          */
 
+static void channel_new(SpiceSession *session, SpiceChannel *channel,
+                        gpointer user_data)
+{
+    SpiceUsbDeviceManager *self = user_data;
+
+    if (SPICE_IS_USBREDIR_CHANNEL(channel))
+        g_ptr_array_add(self->priv->channels, channel);
+}
+
+static void channel_destroy(SpiceSession *session, SpiceChannel *channel,
+                            gpointer user_data)
+{
+    SpiceUsbDeviceManager *self = user_data;
+
+    if (SPICE_IS_USBREDIR_CHANNEL(channel))
+        g_ptr_array_remove(self->priv->channels, channel);
+}
+
 #ifdef USE_USBREDIR
 static gboolean spice_usb_device_manager_source_callback(gpointer user_data)
 {
@@ -392,17 +469,13 @@ static void spice_usb_device_manager_dev_removed(GUsbDeviceList *devlist,
 }
 #endif
 
-struct spice_usb_device_manager_new_params {
-    GMainContext *main_context;
-    GError **err;
-};
-
-static SpiceUsbDeviceManager *spice_usb_device_manager_new(void *p)
+static void
+spice_usb_device_manager_spice_session_destroyed_cb(gpointer user_data,
+                                                    GObject *object)
 {
-    struct spice_usb_device_manager_new_params *params = p;
+    SpiceUsbDeviceManager *self = user_data;
 
-    return g_initable_new(SPICE_TYPE_USB_DEVICE_MANAGER, NULL, params->err,
-                          "main-context", params->main_context, NULL);
+    g_object_unref(self);
 }
 
 /* ------------------------------------------------------------------ */
@@ -429,76 +502,43 @@ static SpiceUsbredirChannel *spice_usb_device_manager_get_channel_for_dev(
 
 /**
  * spice_usb_device_manager_get:
+ * @session: #SpiceSession for which to get the #SpiceUsbDeviceManager
  * @main_context: #GMainContext to use. If %NULL, the default context is used.
  *
- * #SpiceUsbDeviceManager is a singleton, use this function to get a pointer
- * to it. A new #SpiceUsbDeviceManager instance will be created the first
- * time this function is called
+ * Gets the #SpiceUsbDeviceManager associated with the passed in #SpiceSession.
+ * A new #SpiceUsbDeviceManager instance will be created the first time this
+ * function is called for a certain #SpiceSession.
+ *
+ * Note that this function returns a weak reference, which should not be used
+ * after the #SpiceSession itself has been unref-ed by the caller.
  *
- * Returns: (transfer none): a weak reference to the #SpiceUsbDeviceManager singleton
+ * Returns: (transfer none): a weak reference to the #SpiceUsbDeviceManager associated with the passed in #SpiceSession
  */
-SpiceUsbDeviceManager *spice_usb_device_manager_get(GMainContext *main_context,
+SpiceUsbDeviceManager *spice_usb_device_manager_get(SpiceSession *session,
+                                                    GMainContext *main_context,
                                                     GError **err)
 {
-    static GOnce manager_singleton_once = G_ONCE_INIT;
-    struct spice_usb_device_manager_new_params params;
+    SpiceUsbDeviceManager *self;
+    static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
 
     g_return_val_if_fail(err == NULL || *err == NULL, NULL);
 
-    params.main_context = main_context;
-    params.err = err;
-
-    return g_once(&manager_singleton_once,
-                  (GThreadFunc)spice_usb_device_manager_new,
-                  &params);
-}
-
-/**
- * spice_usb_device_manager_register_channel:
- * @manager: the #SpiceUsbDeviceManager manager
- * @channel: a #SpiceUsbredirChannel to register
- *
- * Register @channel to be managed by the USB device @manager.  When a
- * new device is added/plugged, the @manager will use an available
- * channel to establish the redirection with the Spice server.
- *
- * Note that this function takes a weak reference to the channel, it is the
- * callers responsibility to call spice_usb_device_manager_unregister_channel()
- * before it unrefs its own reference.
- **/
-void spice_usb_device_manager_register_channel(SpiceUsbDeviceManager *self,
-                                               SpiceUsbredirChannel *channel)
-{
-    SpiceUsbDeviceManagerPrivate *priv;
-    guint i;
-
-    g_return_if_fail(SPICE_IS_USB_DEVICE_MANAGER(self));
-    g_return_if_fail(SPICE_IS_USBREDIR_CHANNEL(channel));
-
-    priv = self->priv;
-
-    for (i = 0; i < priv->channels->len; i++) {
-        if (g_ptr_array_index(priv->channels, i) == channel) {
-            g_return_if_reached();
-        }
+    g_static_mutex_lock(&mutex);
+    self = g_object_get_data(G_OBJECT(session), "spice-usb-device-manager");
+    if (self == NULL) {
+        self = g_initable_new(SPICE_TYPE_USB_DEVICE_MANAGER, NULL, err,
+                              "session", session,
+                              "main-context", main_context, NULL);
+        g_object_set_data(G_OBJECT(session), "spice-usb-device-manager", self);
+        if (self)
+            /* Ensure we are destroyed together with the SpiceSession */
+            g_object_weak_ref(G_OBJECT(session),
+                          spice_usb_device_manager_spice_session_destroyed_cb,
+                          self);
     }
-    g_ptr_array_add(self->priv->channels, channel);
-}
-
-/**
- * spice_usb_device_manager_unregister_channel:
- * @manager: the #SpiceUsbDeviceManager manager
- * @channel: a #SpiceUsbredirChannel to unregister
- *
- * Remove @channel from the list of USB channels to be managed by @manager.
- */
-void spice_usb_device_manager_unregister_channel(SpiceUsbDeviceManager *self,
-                                                 SpiceUsbredirChannel *channel)
-{
-    g_return_if_fail(SPICE_IS_USB_DEVICE_MANAGER(self));
-    g_return_if_fail(SPICE_IS_USBREDIR_CHANNEL(channel));
+    g_static_mutex_unlock(&mutex);
 
-    g_warn_if_fail(g_ptr_array_remove(self->priv->channels, channel));
+    return self;
 }
 
 /**
diff --git a/gtk/usb-device-manager.h b/gtk/usb-device-manager.h
index 46a5e55..00f8eb2 100644
--- a/gtk/usb-device-manager.h
+++ b/gtk/usb-device-manager.h
@@ -88,14 +88,10 @@ GType spice_usb_device_manager_get_type(void);
 
 gchar *spice_usb_device_get_description(SpiceUsbDevice *device);
 
-SpiceUsbDeviceManager *spice_usb_device_manager_get(GMainContext *main_context,
+SpiceUsbDeviceManager *spice_usb_device_manager_get(SpiceSession *session,
+                                                    GMainContext *main_context,
                                                     GError **err);
 
-void spice_usb_device_manager_register_channel(SpiceUsbDeviceManager *manager,
-                                               SpiceUsbredirChannel *channel);
-void spice_usb_device_manager_unregister_channel(SpiceUsbDeviceManager *manager,
-                                                 SpiceUsbredirChannel *channel);
-
 GPtrArray *spice_usb_device_manager_get_devices(SpiceUsbDeviceManager *manager);
 
 gboolean spice_usb_device_manager_is_device_connected(SpiceUsbDeviceManager *manager,
commit f64c15fce3c236c49db460c0f39d5fbca7397cf3
Author: Hans de Goede <hdegoede at redhat.com>
Date:   Thu Oct 6 19:48:33 2011 +0200

    SpiceDisplay: Add a constructor and construction properties
    
    With SpiceDisplay now passing through auto-clipboard settings to
    SpiceGtkSession, it needs to have its SpiceSession and SpiceGtkSession
    private members initalized at construction time, as
    spice_display_set_property() gets called at construction time.
    
    Currently its SpiceSession and SpiceGtkSession are NULL when that
    happens leading to the g_object_set() calls in spice_display_set_property()
    triggering g_return_if_fail statements inside glib and rightly complaining
    loudly.
    
    This patch fixes this by making the SpiceSession and channel ID construction
    properties and passing them to the g_object_new call in spice_display_new.
    
    Signed-off-by: Hans de Goede <hdegoede at redhat.com>

diff --git a/gtk/spice-widget.c b/gtk/spice-widget.c
index 8b1c9ad..2ee9855 100644
--- a/gtk/spice-widget.c
+++ b/gtk/spice-widget.c
@@ -83,6 +83,8 @@ G_DEFINE_TYPE(SpiceDisplay, spice_display, GTK_TYPE_DRAWING_AREA)
 /* Properties */
 enum {
     PROP_0,
+    PROP_SESSION,
+    PROP_CHANNEL_ID,
     PROP_KEYBOARD_GRAB,
     PROP_MOUSE_GRAB,
     PROP_RESIZE_GUEST,
@@ -130,6 +132,12 @@ static void spice_display_get_property(GObject    *object,
     gboolean boolean;
 
     switch (prop_id) {
+    case PROP_SESSION:
+        g_value_set_object(value, d->session);
+        break;
+    case PROP_CHANNEL_ID:
+        g_value_set_int(value, d->channel_id);
+        break;
     case PROP_KEYBOARD_GRAB:
         g_value_set_boolean(value, d->keyboard_grab_enable);
         break;
@@ -164,6 +172,13 @@ static void spice_display_set_property(GObject      *object,
     SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(display);
 
     switch (prop_id) {
+    case PROP_SESSION:
+        d->session = g_object_ref(g_value_get_object(value));
+        d->gtk_session = spice_gtk_session_get(d->session);
+        break;
+    case PROP_CHANNEL_ID:
+        d->channel_id = g_value_get_int(value);
+        break;
     case PROP_KEYBOARD_GRAB:
         d->keyboard_grab_enable = g_value_get_boolean(value);
         if (d->keyboard_grab_enable) {
@@ -256,7 +271,6 @@ static void spice_display_init(SpiceDisplay *display)
     SpiceDisplayPrivate *d;
 
     d = display->priv = SPICE_DISPLAY_GET_PRIVATE(display);
-    memset(d, 0, sizeof(*d));
 
     gtk_widget_add_events(widget,
                           GDK_STRUCTURE_MASK |
@@ -281,6 +295,42 @@ static void spice_display_init(SpiceDisplay *display)
     d->have_mitshm = true;
 }
 
+static GObject *
+spice_display_constructor(GType                  gtype,
+                          guint                  n_properties,
+                          GObjectConstructParam *properties)
+{
+    GObject *obj;
+    SpiceDisplay *display;
+    SpiceDisplayPrivate *d;
+    GList *list;
+    GList *it;
+
+    {
+        /* Always chain up to the parent constructor */
+        GObjectClass *parent_class;
+        parent_class = G_OBJECT_CLASS(spice_display_parent_class);
+        obj = parent_class->constructor(gtype, n_properties, properties);
+    }
+
+    display = SPICE_DISPLAY(obj);
+    d = SPICE_DISPLAY_GET_PRIVATE(display);
+
+    if (!d->session)
+        g_error("SpiceDisplay constructed without a session");
+
+    g_signal_connect(d->session, "channel-new",
+                     G_CALLBACK(channel_new), display);
+    g_signal_connect(d->session, "channel-destroy",
+                     G_CALLBACK(channel_destroy), display);
+    list = spice_session_get_channels(d->session);
+    for (it = g_list_first(list); it != NULL; it = g_list_next(it)) {
+        channel_new(d->session, it->data, (gpointer*)display);
+    }
+    g_list_free(list);
+
+    return obj;
+}
 
 /**
  * spice_display_set_grab_keys:
@@ -1088,11 +1138,42 @@ static void spice_display_class_init(SpiceDisplayClass *klass)
     gtkwidget_class->configure_event = configure_event;
     gtkwidget_class->scroll_event = scroll_event;
 
+    gobject_class->constructor = spice_display_constructor;
     gobject_class->dispose = spice_display_dispose;
     gobject_class->finalize = spice_display_finalize;
     gobject_class->get_property = spice_display_get_property;
     gobject_class->set_property = spice_display_set_property;
 
+    /**
+     * SpiceDisplay:session:
+     *
+     * #SpiceSession for this #SpiceDisplay
+     *
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_SESSION,
+         g_param_spec_object("session",
+                             "Session",
+                             "SpiceSession",
+                             SPICE_TYPE_SESSION,
+                             G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+                             G_PARAM_STATIC_STRINGS));
+
+    /**
+     * SpiceDisplay:channel-id:
+     *
+     * channel-id for this #SpiceDisplay
+     *
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_CHANNEL_ID,
+         g_param_spec_int("channel-id",
+                          "Channel ID",
+                          "Channel ID for this display",
+                          0, 255, 0,
+                          G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+                          G_PARAM_STATIC_STRINGS));
+
     g_object_class_install_property
         (gobject_class, PROP_KEYBOARD_GRAB,
          g_param_spec_boolean("grab-keyboard",
@@ -1589,28 +1670,8 @@ static void channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer dat
  **/
 SpiceDisplay *spice_display_new(SpiceSession *session, int id)
 {
-    SpiceDisplay *display;
-    SpiceDisplayPrivate *d;
-    GList *list;
-    GList *it;
-
-    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",
-                     G_CALLBACK(channel_new), display);
-    g_signal_connect(session, "channel-destroy",
-                     G_CALLBACK(channel_destroy), display);
-    list = spice_session_get_channels(session);
-    for (it = g_list_first(list); it != NULL; it = g_list_next(it)) {
-        channel_new(session, it->data, (gpointer*)display);
-    }
-    g_list_free(list);
-
-    return display;
+    return g_object_new(SPICE_TYPE_DISPLAY, "session", session,
+                        "channel-id", id, NULL);
 }
 
 /**
commit 91eeacbeef798c7da31733073d3752816ff5abc9
Author: Hans de Goede <hdegoede at redhat.com>
Date:   Tue Oct 4 16:15:07 2011 +0200

    Move clipboard handling to SpiceGtkSession
    
    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>

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 41793a9..82440c0 100644
--- a/gtk/map-file
+++ b/gtk/map-file
@@ -69,6 +69,8 @@ spice_session_open_fd;
 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 165d91e..d3cdd7d 100644
--- a/gtk/spice-gtk-session.c
+++ b/gtk/spice-gtk-session.c
@@ -16,10 +16,25 @@
    License along with this library; if not, see <http://www.gnu.org/licenses/>.
 */
 
+#include <gtk/gtk.h>
+#include <spice/vd_agent.h>
+#include "spice-common.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];
 };
 
 /**
@@ -51,6 +66,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) \
@@ -62,11 +87,21 @@ 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 *self)
 {
-    self->priv = SPICE_GTK_SESSION_GET_PRIVATE(self);
+    SpiceGtkSessionPrivate *s;
+
+    s = self->priv = SPICE_GTK_SESSION_GET_PRIVATE(self);
+
+    s->clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
+    g_signal_connect(G_OBJECT(s->clipboard), "owner-change",
+                     G_CALLBACK(clipboard_owner_change), self);
+    s->clipboard_primary = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
+    g_signal_connect(G_OBJECT(s->clipboard_primary), "owner-change",
+                     G_CALLBACK(clipboard_owner_change), self);
 }
 
 static GObject *
@@ -76,6 +111,9 @@ spice_gtk_session_constructor(GType                  gtype,
 {
     GObject *obj;
     SpiceGtkSession *self;
+    SpiceGtkSessionPrivate *s;
+    GList *list;
+    GList *it;
 
     {
         /* Always chain up to the parent constructor */
@@ -85,20 +123,50 @@ spice_gtk_session_constructor(GType                  gtype,
     }
 
     self = SPICE_GTK_SESSION(obj);
-    if (!self->priv->session)
+    s = SPICE_GTK_SESSION_GET_PRIVATE(self);
+    if (!s->session)
         g_error("SpiceGtKSession constructed without a session");
 
+    g_signal_connect(s->session, "channel-new",
+                     G_CALLBACK(channel_new), self);
+    g_signal_connect(s->session, "channel-destroy",
+                     G_CALLBACK(channel_destroy), self);
+    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*)self);
+    }
+    g_list_free(list);
+
     return obj;
 }
 
 static void spice_gtk_session_dispose(GObject *gobject)
 {
-#if 0
     SpiceGtkSession *self = SPICE_GTK_SESSION(gobject);
     SpiceGtkSessionPrivate *s = SPICE_GTK_SESSION_GET_PRIVATE(self);
-#endif
 
     /* release stuff */
+    if (s->clipboard) {
+        g_signal_handlers_disconnect_by_func(s->clipboard,
+                G_CALLBACK(clipboard_owner_change), self);
+        s->clipboard = NULL;
+    }
+
+    if (s->clipboard_primary) {
+        g_signal_handlers_disconnect_by_func(s->clipboard_primary,
+                G_CALLBACK(clipboard_owner_change), self);
+        s->clipboard_primary = NULL;
+    }
+
+    if (s->session) {
+        g_signal_handlers_disconnect_by_func(s->session,
+                                             G_CALLBACK(channel_new),
+                                             self);
+        g_signal_handlers_disconnect_by_func(s->session,
+                                             G_CALLBACK(channel_destroy),
+                                             self);
+        s->session = NULL;
+    }
 
     /* Chain up to the parent class */
     if (G_OBJECT_CLASS(spice_gtk_session_parent_class)->dispose)
@@ -107,12 +175,15 @@ static void spice_gtk_session_dispose(GObject *gobject)
 
 static void spice_gtk_session_finalize(GObject *gobject)
 {
-#if 0
     SpiceGtkSession *self = SPICE_GTK_SESSION(gobject);
     SpiceGtkSessionPrivate *s = SPICE_GTK_SESSION_GET_PRIVATE(self);
-#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)
@@ -131,6 +202,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;
@@ -149,6 +223,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;
@@ -181,6 +258,23 @@ 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));
 }
 
@@ -193,6 +287,446 @@ spice_gtk_session_spice_session_destroyed_cb(gpointer user_data,
     g_object_unref(self);
 }
 
+/* ---------------------------------------------------------------- */
+/* 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 *self = user_data;
+    SpiceGtkSessionPrivate *s = SPICE_GTK_SESSION_GET_PRIVATE(self);
+    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 *self = user_data;
+    SpiceGtkSessionPrivate *s = SPICE_GTK_SESSION_GET_PRIVATE(self);
+    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,
+                                          self);
+        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 *self = user_data;
+    SpiceGtkSessionPrivate *s = SPICE_GTK_SESSION_GET_PRIVATE(self);
+    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 *self = user_data;
+    SpiceGtkSessionPrivate *s = SPICE_GTK_SESSION_GET_PRIVATE(self);
+    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, self)) {
+        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 *self = user_data;
+    SpiceGtkSessionPrivate *s = SPICE_GTK_SESSION_GET_PRIVATE(self);
+    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 *self = user_data;
+    SpiceGtkSessionPrivate *s = SPICE_GTK_SESSION_GET_PRIVATE(self);
+    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, self);
+
+    return TRUE;
+}
+
+static void clipboard_release(SpiceMainChannel *main, guint selection,
+                              gpointer user_data)
+{
+    SpiceGtkSession *self = user_data;
+    SpiceGtkSessionPrivate *s = SPICE_GTK_SESSION_GET_PRIVATE(self);
+    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 *self = user_data;
+    SpiceGtkSessionPrivate *s = SPICE_GTK_SESSION_GET_PRIVATE(self);
+
+    if (SPICE_IS_MAIN_CHANNEL(channel)) {
+        s->main = SPICE_MAIN_CHANNEL(channel);
+        g_signal_connect(channel, "main-clipboard-selection-grab",
+                         G_CALLBACK(clipboard_grab), self);
+        g_signal_connect(channel, "main-clipboard-selection-request",
+                         G_CALLBACK(clipboard_request), self);
+        g_signal_connect(channel, "main-clipboard-selection-release",
+                         G_CALLBACK(clipboard_release), self);
+    }
+}
+
+static void channel_destroy(SpiceSession *session, SpiceChannel *channel,
+                            gpointer user_data)
+{
+    SpiceGtkSession *self = user_data;
+    SpiceGtkSessionPrivate *s = SPICE_GTK_SESSION_GET_PRIVATE(self);
+    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                                                   */
 
@@ -228,3 +762,48 @@ SpiceGtkSession *spice_gtk_session_get(SpiceSession *session)
 
     return SPICE_GTK_SESSION(self);
 }
+
+/**
+ * spice_gtk_session_copy_to_guest:
+ * @self:
+ *
+ * Copy client-side clipboard to guest clipboard.
+ **/
+void spice_gtk_session_copy_to_guest(SpiceGtkSession *self)
+{
+    SpiceGtkSessionPrivate *s = SPICE_GTK_SESSION_GET_PRIVATE(self);
+    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,
+                                      self);
+    }
+}
+
+/**
+ * spice_gtk_session_paste_from_guest:
+ * @self:
+ *
+ * Copy guest clipboard to client-side clipboard.
+ **/
+void spice_gtk_session_paste_from_guest(SpiceGtkSession *self)
+{
+    SpiceGtkSessionPrivate *s = SPICE_GTK_SESSION_GET_PRIVATE(self);
+    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, self)) {
+        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..3b4eac6 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 *self);
+void spice_gtk_session_paste_from_guest(SpiceGtkSession *self);
 
 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);
 }
 
 /**
commit f6766163d27e0a704613bedb6b5c7fdf89b474af
Author: Hans de Goede <hdegoede at redhat.com>
Date:   Tue Oct 4 10:38:36 2011 +0200

    Add a SpiceGtkSession Class
    
    This initial commit of the SpiceGtkSession Class only adds the empty
    class and the 1:1 linkage to SpiceSession through 2 new private methods
    added to SpiceSession: spice_session_{get|set}_gtk_session.
    
    The following commits will move things which are currently per SpiceDisplay,
    but which really should be global, such as the clipboard, over to
    SpiceGtkSession.
    
    Signed-off-by: Hans de Goede <hdegoede at redhat.com>

diff --git a/doc/reference/spice-gtk-docs.xml b/doc/reference/spice-gtk-docs.xml
index c7f205b..2b4336d 100644
--- a/doc/reference/spice-gtk-docs.xml
+++ b/doc/reference/spice-gtk-docs.xml
@@ -40,6 +40,7 @@
 
     <chapter>
       <title>GTK Widget, from spice-client-gtk</title>
+      <xi:include href="xml/spice-gtk-session.xml"/>
       <xi:include href="xml/spice-widget.xml"/>
     </chapter>
 
diff --git a/doc/reference/spice-gtk-sections.txt b/doc/reference/spice-gtk-sections.txt
index d789d5a..d5e8e70 100644
--- a/doc/reference/spice-gtk-sections.txt
+++ b/doc/reference/spice-gtk-sections.txt
@@ -287,6 +287,24 @@ SpiceUsbDeviceManagerPrivate
 </SECTION>
 
 <SECTION>
+<FILE>spice-gtk-session</FILE>
+<TITLE>SpiceGtkSession</TITLE>
+SpiceGtkSession
+SpiceGtkSessionClass
+spice_gtk_session_get
+<SUBSECTION Standard>
+SPICE_GTK_SESSION
+SPICE_IS_GTK_SESSION
+SPICE_TYPE_GTK_SESSION
+spice_gtk_session_get_type
+SPICE_GTK_SESSION_CLASS
+SPICE_IS_GTK_SESSION_CLASS
+SPICE_GTK_SESSION_GET_CLASS
+<SUBSECTION Private>
+SpiceGtkSectionPrivate
+</SECTION>
+
+<SECTION>
 <FILE>spice-widget</FILE>
 <TITLE>SpiceDisplay</TITLE>
 SpiceDisplay
diff --git a/doc/reference/spice-gtk.types b/doc/reference/spice-gtk.types
index d8e0f28..a88ece1 100644
--- a/doc/reference/spice-gtk.types
+++ b/doc/reference/spice-gtk.types
@@ -13,6 +13,7 @@
 #include "channel-record.h"
 #include "channel-smartcard.h"
 #include "channel-usbredir.h"
+#include "spice-gtk-session.h"
 #include "spice-widget.h"
 #include "spice-grabsequence.h"
 #include "smartcard-manager.h"
@@ -25,6 +26,7 @@ spice_cursor_channel_get_type
 spice_display_channel_get_type
 spice_display_get_type
 spice_grab_sequence_get_type
+spice_gtk_session_get_type
 spice_inputs_channel_get_type
 spice_inputs_lock_get_type
 spice_main_channel_get_type
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index 7d197f6..f4b595f 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -97,6 +97,7 @@ SPICE_GTK_LIBADD_COMMON =		\
 	$(NULL)
 
 SPICE_GTK_SOURCES_COMMON =		\
+	spice-gtk-session.c		\
 	spice-widget.c			\
 	spice-widget-priv.h		\
 	vncdisplaykeymap.c		\
@@ -134,6 +135,7 @@ endif
 
 libspice_client_gtkincludedir = $(includedir)/spice-client-gtk-$(SPICE_GTK_API_VERSION)
 libspice_client_gtkinclude_HEADERS =	\
+	spice-gtk-session.h		\
 	spice-widget.h			\
 	spice-grabsequence.h		\
 	$(NULL)
@@ -562,6 +564,7 @@ glib_introspection_files =			\
 gtk_introspection_files =			\
 	$(libspice_client_gtkinclude_HEADERS)	\
 	$(nodist_libspice_client_gtkinclude_HEADERS)	\
+	spice-gtk-session.c			\
 	spice-widget.c				\
 	spice-grabsequence.c			\
 	$(NULL)
diff --git a/gtk/map-file b/gtk/map-file
index b383edf..41793a9 100644
--- a/gtk/map-file
+++ b/gtk/map-file
@@ -67,6 +67,8 @@ spice_session_migration_get_type;
 spice_session_new;
 spice_session_open_fd;
 spice_session_verify_get_type;
+spice_gtk_session_get;
+spice_gtk_session_get_type;
 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
new file mode 100644
index 0000000..165d91e
--- /dev/null
+++ b/gtk/spice-gtk-session.c
@@ -0,0 +1,230 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2010-2011 Red Hat, Inc.
+
+   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 2.1 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 Lesser General Public
+   License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "spice-gtk-session.h"
+
+struct _SpiceGtkSessionPrivate {
+    SpiceSession *session;
+};
+
+/**
+ * SECTION:spice-gtk-session
+ * @short_description: handles GTK connection details
+ * @title: Spice GTK Session
+ * @section_id:
+ * @see_also: #SpiceSession, and the GTK widget #SpiceDisplay
+ * @stability: Stable
+ * @include: spice-gtk-session.h
+ *
+ * The #SpiceGtkSession class is the spice-client-gtk counter part of
+ * #SpiceSession. It contains functionality which should be handled per
+ * session rather then per #SpiceDisplay (one session can have multiple
+ * displays), but which cannot live in #SpiceSession as it depends on
+ * GTK. For example the clipboard functionality.
+ *
+ * There should always be a 1:1 relation between #SpiceGtkSession objects
+ * and #SpiceSession objects. Therefor there is no spice_gtk_session_new,
+ * instead there is spice_gtk_session_get() which ensures this 1:1 relation.
+ *
+ * #SpiceDisplay uses #SpiceGtkSession internally, some #SpiceDisplay
+ * properties map directly to #SpiceGtkSession properties, this means that
+ * changing them for one #SpiceDisplay changes them for all displays.
+ *
+ * Depending on your UI, you may want to not show these properties on a
+ * per display basis and instead show them in a global settings menu which
+ * directly uses SpiceGtkSession.
+ */
+
+/* ------------------------------------------------------------------ */
+/* gobject glue                                                       */
+
+#define SPICE_GTK_SESSION_GET_PRIVATE(obj) \
+    (G_TYPE_INSTANCE_GET_PRIVATE ((obj), SPICE_TYPE_GTK_SESSION, SpiceGtkSessionPrivate))
+
+G_DEFINE_TYPE (SpiceGtkSession, spice_gtk_session, G_TYPE_OBJECT);
+
+/* Properties */
+enum {
+    PROP_0,
+    PROP_SESSION,
+};
+
+static void spice_gtk_session_init(SpiceGtkSession *self)
+{
+    self->priv = SPICE_GTK_SESSION_GET_PRIVATE(self);
+}
+
+static GObject *
+spice_gtk_session_constructor(GType                  gtype,
+                              guint                  n_properties,
+                              GObjectConstructParam *properties)
+{
+    GObject *obj;
+    SpiceGtkSession *self;
+
+    {
+        /* Always chain up to the parent constructor */
+        GObjectClass *parent_class;
+        parent_class = G_OBJECT_CLASS(spice_gtk_session_parent_class);
+        obj = parent_class->constructor(gtype, n_properties, properties);
+    }
+
+    self = SPICE_GTK_SESSION(obj);
+    if (!self->priv->session)
+        g_error("SpiceGtKSession constructed without a session");
+
+    return obj;
+}
+
+static void spice_gtk_session_dispose(GObject *gobject)
+{
+#if 0
+    SpiceGtkSession *self = SPICE_GTK_SESSION(gobject);
+    SpiceGtkSessionPrivate *s = SPICE_GTK_SESSION_GET_PRIVATE(self);
+#endif
+
+    /* release stuff */
+
+    /* Chain up to the parent class */
+    if (G_OBJECT_CLASS(spice_gtk_session_parent_class)->dispose)
+        G_OBJECT_CLASS(spice_gtk_session_parent_class)->dispose(gobject);
+}
+
+static void spice_gtk_session_finalize(GObject *gobject)
+{
+#if 0
+    SpiceGtkSession *self = SPICE_GTK_SESSION(gobject);
+    SpiceGtkSessionPrivate *s = SPICE_GTK_SESSION_GET_PRIVATE(self);
+#endif
+
+    /* release stuff */
+
+    /* Chain up to the parent class */
+    if (G_OBJECT_CLASS(spice_gtk_session_parent_class)->finalize)
+        G_OBJECT_CLASS(spice_gtk_session_parent_class)->finalize(gobject);
+}
+
+static void spice_gtk_session_get_property(GObject    *gobject,
+                                           guint       prop_id,
+                                           GValue     *value,
+                                           GParamSpec *pspec)
+{
+    SpiceGtkSession *self = SPICE_GTK_SESSION(gobject);
+    SpiceGtkSessionPrivate *s = SPICE_GTK_SESSION_GET_PRIVATE(self);
+
+    switch (prop_id) {
+    case PROP_SESSION:
+        g_value_set_object(value, s->session);
+	break;
+    default:
+	G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+	break;
+    }
+}
+
+static void spice_gtk_session_set_property(GObject      *gobject,
+                                           guint         prop_id,
+                                           const GValue *value,
+                                           GParamSpec   *pspec)
+{
+    SpiceGtkSession *self = SPICE_GTK_SESSION(gobject);
+    SpiceGtkSessionPrivate *s = SPICE_GTK_SESSION_GET_PRIVATE(self);
+
+    switch (prop_id) {
+    case PROP_SESSION:
+        s->session = g_value_get_object(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+        break;
+    }
+}
+
+static void spice_gtk_session_class_init(SpiceGtkSessionClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+
+    gobject_class->constructor  = spice_gtk_session_constructor;
+    gobject_class->dispose      = spice_gtk_session_dispose;
+    gobject_class->finalize     = spice_gtk_session_finalize;
+    gobject_class->get_property = spice_gtk_session_get_property;
+    gobject_class->set_property = spice_gtk_session_set_property;
+
+    /**
+     * SpiceGtkSession:session:
+     *
+     * #SpiceSession this #SpiceGtkSession is associated with
+     *
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_SESSION,
+         g_param_spec_object("session",
+                             "Session",
+                             "SpiceSession",
+                             SPICE_TYPE_SESSION,
+                             G_PARAM_READWRITE |
+                             G_PARAM_CONSTRUCT_ONLY |
+                             G_PARAM_STATIC_STRINGS));
+
+    g_type_class_add_private(klass, sizeof(SpiceGtkSessionPrivate));
+}
+
+static void
+spice_gtk_session_spice_session_destroyed_cb(gpointer user_data,
+                                             GObject *object)
+{
+    SpiceGtkSession *self = user_data;
+
+    g_object_unref(self);
+}
+
+/* ------------------------------------------------------------------ */
+/* public functions                                                   */
+
+/**
+ * spice_gtk_session_get:
+ * @session: #SpiceSession for which to get the #SpiceGtkSession
+ *
+ * Gets the #SpiceGtkSession associated with the passed in #SpiceSession.
+ * A new #SpiceGtkSession instance will be created the first time this
+ * function is called for a certain #SpiceSession.
+ *
+ * Note that this function returns a weak reference, which should not be used
+ * after the #SpiceSession itself has been unref-ed by the caller.
+ *
+ * Returns: (transfer none): a weak reference to the #SpiceGtkSession associated with the passed in #SpiceSession
+ **/
+SpiceGtkSession *spice_gtk_session_get(SpiceSession *session)
+{
+    GObject *self;
+    static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
+
+    g_static_mutex_lock(&mutex);
+    self = g_object_get_data(G_OBJECT(session), "spice-gtk-session");
+    if (self == NULL) {
+        self = g_object_new(SPICE_TYPE_GTK_SESSION, "session", session, NULL);
+        g_object_set_data(G_OBJECT(session), "spice-gtk-session", self);
+        /* Ensure we are destroyed together with the SpiceSession */
+        g_object_weak_ref(G_OBJECT(session),
+                          spice_gtk_session_spice_session_destroyed_cb,
+                          self);
+    }
+    g_static_mutex_unlock(&mutex);
+
+    return SPICE_GTK_SESSION(self);
+}
diff --git a/gtk/spice-gtk-session.h b/gtk/spice-gtk-session.h
new file mode 100644
index 0000000..9c59fa2
--- /dev/null
+++ b/gtk/spice-gtk-session.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2010-2011 Red Hat, Inc.
+
+   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 2.1 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 Lesser General Public
+   License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef __SPICE_CLIENT_GTK_SESSION_H__
+#define __SPICE_CLIENT_GTK_SESSION_H__
+
+#include "spice-client.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_GTK_SESSION            (spice_gtk_session_get_type ())
+#define SPICE_GTK_SESSION(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SPICE_TYPE_GTK_SESSION, SpiceGtkSession))
+#define SPICE_GTK_SESSION_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SPICE_TYPE_GTK_SESSION, SpiceGtkSessionClass))
+#define SPICE_IS_GTK_SESSION(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SPICE_TYPE_GTK_SESSION))
+#define SPICE_IS_GTK_SESSION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SPICE_TYPE_GTK_SESSION))
+#define SPICE_GTK_SESSION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SPICE_TYPE_GTK_SESSION, SpiceGtkSessionClass))
+
+typedef struct _SpiceGtkSession SpiceGtkSession;
+typedef struct _SpiceGtkSessionClass SpiceGtkSessionClass;
+typedef struct _SpiceGtkSessionPrivate SpiceGtkSessionPrivate;
+
+struct _SpiceGtkSession
+{
+    GObject parent;
+    SpiceGtkSessionPrivate *priv;
+    /* Do not add fields to this struct */
+};
+
+struct _SpiceGtkSessionClass
+{
+    GObjectClass parent_class;
+
+    /* signals */
+
+    /*< private >*/
+    /*
+     * If adding fields to this struct, remove corresponding
+     * amount of padding to avoid changing overall struct size
+     */
+    gchar _spice_reserved[SPICE_RESERVED_PADDING];
+};
+
+GType spice_gtk_session_get_type(void);
+
+SpiceGtkSession *spice_gtk_session_get(SpiceSession *session);
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_GTK_SESSION_H__ */
commit 6794969f7dc1137febbf0466c15a76d9295c6d63
Author: Hans de Goede <hdegoede at redhat.com>
Date:   Tue Oct 4 12:52:25 2011 +0200

    Fixup some headers so that they include headers the depend up on.
    
    Otherwise they cannot be included unless other headers are included
    first (and in the right order).
    
    Signed-off-by: Hans de Goede <hdegoede at redhat.com>

diff --git a/gtk/spice-channel-cache.h b/gtk/spice-channel-cache.h
index 07acb5c..d8a40f3 100644
--- a/gtk/spice-channel-cache.h
+++ b/gtk/spice-channel-cache.h
@@ -19,6 +19,8 @@
 # define SPICE_CHANNEL_CACHE_H_
 
 /* spice/common */
+#include <inttypes.h> /* For PRIx64 */
+#include "mem.h"
 #include "ring.h"
 
 G_BEGIN_DECLS
diff --git a/gtk/spice-session-priv.h b/gtk/spice-session-priv.h
index b135960..2815ed2 100644
--- a/gtk/spice-session-priv.h
+++ b/gtk/spice-session-priv.h
@@ -20,6 +20,7 @@
 
 #include <glib.h>
 #include <gio/gio.h>
+#include "spice-session.h"
 #include "spice-channel-cache.h"
 #include "decode.h"
 


More information about the Spice-commits mailing list