[Spice-commits] 16 commits - doc/reference gtk/channel-base.c gtk/channel-inputs.c gtk/channel-inputs.h gtk/channel-main.c gtk/channel-smartcard.c gtk/map-file gtk/spice-channel.c gtk/spice-channel.h gtk/spice-channel-priv.h gtk/spice-session.c gtk/spice-session-priv.h gtk/spice-util.c gtk/spice-util-priv.h gtk/spice-widget.c gtk/spice-widget-priv.h gtk/usb-device-manager.c NEWS spice-common

Marc-André Lureau elmarco at kemper.freedesktop.org
Tue Aug 28 09:57:32 PDT 2012


 NEWS                                 |   12 ++
 doc/reference/spice-gtk-sections.txt |    1 
 gtk/channel-base.c                   |   15 +--
 gtk/channel-inputs.c                 |   59 ++++++++----
 gtk/channel-inputs.h                 |    1 
 gtk/channel-main.c                   |  171 +++++++++++++++++++++++++++++++----
 gtk/channel-smartcard.c              |   55 +++++++----
 gtk/map-file                         |    1 
 gtk/spice-channel-priv.h             |    3 
 gtk/spice-channel.c                  |   41 +++++---
 gtk/spice-channel.h                  |    6 +
 gtk/spice-session-priv.h             |    6 +
 gtk/spice-session.c                  |   16 ++-
 gtk/spice-util-priv.h                |    1 
 gtk/spice-util.c                     |   21 ++++
 gtk/spice-widget-priv.h              |    3 
 gtk/spice-widget.c                   |  124 ++++++++++++++++++++++---
 gtk/usb-device-manager.c             |    4 
 spice-common                         |    2 
 19 files changed, 441 insertions(+), 101 deletions(-)

New commits:
commit 0d5ad7e22d55552ef4be8368dcfbae879fb0b77e
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date:   Wed Aug 15 21:40:54 2012 +0300

    widget: differentiate key press & release from press only events
    
    Until now, Spice clients only sent separate key events for press and
    release. But this may result in unwanted key repetition from guest VM
    side. It seems OSes have various implementations. While MS Windows
    relies on hardware key repeats (which are several sequential press
    events), otoh, X11 uses software key repeat (although not Linux
    keyboard/VT by default).
    
    We can't easily disable guest side repeaters, as it may be enforced by
    other components (a X11 client can adjust each key individually, or
    the desktop settings may change it etc.). Neither can we rely only on
    guest software repeater as Windows doesn't seem to have one by
    default, so we need to keep sending multiple press events as of today.
    
    It seems a nice way to improve the situation is to send a single
    "press&release" key event when the user released the key within a
    short delay. If the key is pressed for longer, we keep the existing
    behaviour which has been working pretty well so far, sending separate
    "press", then repeatedly "press", and an ending "release" event.
    
    v2:
    - fix some commit message spelling spotted by Alon & Christophe
    - simplify a bit the timer handling code after Hans review
    - remove the submodule change (will be updated in earler patch once
      pushed upstream)

diff --git a/gtk/spice-widget-priv.h b/gtk/spice-widget-priv.h
index 97c6489..87d1de9 100644
--- a/gtk/spice-widget-priv.h
+++ b/gtk/spice-widget-priv.h
@@ -106,6 +106,8 @@ struct _SpiceDisplayPrivate {
     const guint16 const     *keycode_map;
     size_t                  keycode_maplen;
     uint32_t                key_state[512 / 32];
+    int                     key_delayed_scancode;
+    guint                   key_delayed_id;
     SpiceGrabSequence         *grabseq; /* the configured key sequence */
     gboolean                *activeseq; /* the currently pressed keys */
     gint                    mark;
diff --git a/gtk/spice-widget.c b/gtk/spice-widget.c
index 014fdaa..5fe36de 100644
--- a/gtk/spice-widget.c
+++ b/gtk/spice-widget.c
@@ -393,6 +393,11 @@ static void spice_display_dispose(GObject *obj)
     g_clear_object(&d->session);
     d->gtk_session = NULL;
 
+    if (d->key_delayed_id) {
+        g_source_remove(d->key_delayed_id);
+        d->key_delayed_id = 0;
+    }
+
     G_OBJECT_CLASS(spice_display_parent_class)->dispose(obj);
 }
 
@@ -994,6 +999,40 @@ typedef enum {
     SEND_KEY_RELEASE,
 } SendKeyType;
 
+static void key_press_and_release(SpiceDisplay *display)
+{
+    SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(display);
+
+    if (d->key_delayed_scancode == 0)
+        return;
+
+    spice_inputs_key_press_and_release(d->inputs, d->key_delayed_scancode);
+    d->key_delayed_scancode = 0;
+
+    if (d->key_delayed_id) {
+        g_source_remove(d->key_delayed_id);
+        d->key_delayed_id = 0;
+    }
+}
+
+static gboolean key_press_delayed(gpointer data)
+{
+    SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(data);
+
+    if (d->key_delayed_scancode == 0)
+        return FALSE;
+
+    spice_inputs_key_press(d->inputs, d->key_delayed_scancode);
+    d->key_delayed_scancode = 0;
+
+    if (d->key_delayed_id) {
+        g_source_remove(d->key_delayed_id);
+        d->key_delayed_id = 0;
+    }
+
+    return FALSE;
+}
+
 static void send_key(SpiceDisplay *display, int scancode, SendKeyType type, gboolean press_delayed)
 {
     SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(display);
@@ -1010,15 +1049,40 @@ static void send_key(SpiceDisplay *display, int scancode, SendKeyType type, gboo
     m = (1 << b);
     g_return_if_fail(i < SPICE_N_ELEMENTS(d->key_state));
 
-    if (type == SEND_KEY_PRESS) {
-        spice_inputs_key_press(d->inputs, scancode);
+    switch (type) {
+    case SEND_KEY_PRESS:
+        /* ensure delayed key is pressed before any new input event */
+        key_press_delayed(display);
+
+        if (press_delayed &&
+            d->keypress_delay != 0 &&
+            !(d->key_state[i] & m)) {
+            g_warn_if_fail(d->key_delayed_id == 0);
+            d->key_delayed_id = g_timeout_add(d->keypress_delay, key_press_delayed, display);
+            d->key_delayed_scancode = scancode;
+        } else
+            spice_inputs_key_press(d->inputs, scancode);
+
         d->key_state[i] |= m;
-    } else {
-        if (!(d->key_state[i] & m)) {
-            return;
+        break;
+
+    case SEND_KEY_RELEASE:
+        if (!(d->key_state[i] & m))
+            break;
+
+        if (d->key_delayed_scancode == scancode)
+            key_press_and_release(display);
+        else {
+            /* ensure delayed key is pressed before other key are released */
+            key_press_delayed(display);
+            spice_inputs_key_release(d->inputs, scancode);
         }
-        spice_inputs_key_release(d->inputs, scancode);
+
         d->key_state[i] &= ~m;
+        break;
+
+    default:
+        g_warn_if_reached();
     }
 }
 
commit c03e002152dc0c7ced046706b0d6e23dc89ab53d
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date:   Wed Aug 15 20:55:44 2012 +0300

    widget: add keypress-delay property
    
    The delay before the press event is sent to the server if the key is
    kept pressed. If the key is released within that time, that delay is
    ignored and a single key-press-release event will be sent.

diff --git a/gtk/spice-widget-priv.h b/gtk/spice-widget-priv.h
index 87cf34e..97c6489 100644
--- a/gtk/spice-widget-priv.h
+++ b/gtk/spice-widget-priv.h
@@ -112,6 +112,7 @@ struct _SpiceDisplayPrivate {
 #ifdef WIN32
     HHOOK                   keyboard_hook;
 #endif
+    guint                   keypress_delay;
     gint                    zoom_level;
 };
 
diff --git a/gtk/spice-widget.c b/gtk/spice-widget.c
index cbd5f4b..014fdaa 100644
--- a/gtk/spice-widget.c
+++ b/gtk/spice-widget.c
@@ -103,6 +103,7 @@ enum {
     PROP_DISABLE_INPUTS,
     PROP_ZOOM_LEVEL,
     PROP_MONITOR_ID,
+    PROP_KEYPRESS_DELAY,
     PROP_READY
 };
 
@@ -179,6 +180,9 @@ static void spice_display_get_property(GObject    *object,
     case PROP_READY:
         g_value_set_boolean(value, d->ready);
         break;
+    case PROP_KEYPRESS_DELAY:
+        g_value_set_uint(value, d->keypress_delay);
+        break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
         break;
@@ -347,6 +351,9 @@ static void spice_display_set_property(GObject      *object,
         d->zoom_level = g_value_get_int(value);
         scaling_updated(display);
         break;
+    case PROP_KEYPRESS_DELAY:
+        d->keypress_delay = g_value_get_uint(value);
+        break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
         break;
@@ -1561,6 +1568,26 @@ static void spice_display_class_init(SpiceDisplayClass *klass)
                               G_PARAM_STATIC_STRINGS));
 
     /**
+     * SpiceDisplay:keypress-delay:
+     *
+     * Delay in ms of non-modifiers key press events. If the key is
+     * released before this delay, a single press & release event is
+     * sent to the server. If the key is pressed longer than the
+     * keypress-delay, the server will receive the delayed press
+     * event, and a following release event when the key is released.
+     *
+     * Since: 0.13
+     **/
+    g_object_class_install_property
+        (gobject_class, PROP_KEYPRESS_DELAY,
+         g_param_spec_uint("keypress-delay", "Keypress delay",
+                           "Keypress delay",
+                           0, G_MAXUINT, 100,
+                           G_PARAM_READWRITE |
+                           G_PARAM_CONSTRUCT |
+                           G_PARAM_STATIC_STRINGS));
+
+    /**
      * SpiceDisplay:disable-inputs:
      *
      * Disable all keyboard & mouse inputs.
commit 7bd86c31fce34b13c6846020b179a6a5c4a647fd
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date:   Wed Aug 15 21:09:22 2012 +0300

    widget: give more context to send_key()
    
    - use a more explicit SendKeyType enum
    - if the key is a modifier key, we don't want to delay press event
    
    v2: fix compilation (remove down usage)

diff --git a/gtk/spice-widget.c b/gtk/spice-widget.c
index 2bb7b38..cbd5f4b 100644
--- a/gtk/spice-widget.c
+++ b/gtk/spice-widget.c
@@ -982,8 +982,12 @@ static gboolean expose_event(GtkWidget *widget, GdkEventExpose *expose)
 #endif
 
 /* ---------------------------------------------------------------- */
+typedef enum {
+    SEND_KEY_PRESS,
+    SEND_KEY_RELEASE,
+} SendKeyType;
 
-static void send_key(SpiceDisplay *display, int scancode, int down)
+static void send_key(SpiceDisplay *display, int scancode, SendKeyType type, gboolean press_delayed)
 {
     SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(display);
     uint32_t i, b, m;
@@ -999,7 +1003,7 @@ static void send_key(SpiceDisplay *display, int scancode, int down)
     m = (1 << b);
     g_return_if_fail(i < SPICE_N_ELEMENTS(d->key_state));
 
-    if (down) {
+    if (type == SEND_KEY_PRESS) {
         spice_inputs_key_press(d->inputs, scancode);
         d->key_state[i] |= m;
     } else {
@@ -1022,7 +1026,7 @@ static void release_keys(SpiceDisplay *display)
             continue;
         }
         for (b = 0; b < 32; b++) {
-            send_key(display, i * 32 + b, 0);
+            send_key(display, i * 32 + b, SEND_KEY_RELEASE, FALSE);
         }
     }
 }
@@ -1065,9 +1069,10 @@ static gboolean key_event(GtkWidget *widget, GdkEventKey *key)
     SpiceDisplayPrivate *d = SPICE_DISPLAY_GET_PRIVATE(display);
     int scancode;
 
-    SPICE_DEBUG("%s %s: keycode: %d  state: %d  group %d",
+
+    SPICE_DEBUG("%s %s: keycode: %d  state: %d  group %d modifier %d",
             __FUNCTION__, key->type == GDK_KEY_PRESS ? "press" : "release",
-            key->hardware_keycode, key->state, key->group);
+            key->hardware_keycode, key->state, key->group, key->is_modifier);
 
     if (check_for_grab_key(display, key->type, key->keyval)) {
         g_signal_emit(widget, signals[SPICE_DISPLAY_GRAB_KEY_PRESSED], 0);
@@ -1092,10 +1097,10 @@ static gboolean key_event(GtkWidget *widget, GdkEventKey *key)
                                             key->hardware_keycode);
     switch (key->type) {
     case GDK_KEY_PRESS:
-        send_key(display, scancode, 1);
+        send_key(display, scancode, SEND_KEY_PRESS, !key->is_modifier);
         break;
     case GDK_KEY_RELEASE:
-        send_key(display, scancode, 0);
+        send_key(display, scancode, SEND_KEY_RELEASE, !key->is_modifier);
         break;
     default:
         g_warn_if_reached();
@@ -1134,12 +1139,12 @@ void spice_display_send_keys(SpiceDisplay *display, const guint *keyvals,
 
     if (kind & SPICE_DISPLAY_KEY_EVENT_PRESS) {
         for (i = 0 ; i < nkeyvals ; i++)
-            send_key(display, get_scancode_from_keyval(display, keyvals[i]), 1);
+            send_key(display, get_scancode_from_keyval(display, keyvals[i]), SEND_KEY_PRESS, FALSE);
     }
 
     if (kind & SPICE_DISPLAY_KEY_EVENT_RELEASE) {
         for (i = (nkeyvals-1) ; i >= 0 ; i--)
-            send_key(display, get_scancode_from_keyval(display, keyvals[i]), 0);
+            send_key(display, get_scancode_from_keyval(display, keyvals[i]), SEND_KEY_RELEASE, FALSE);
     }
 }
 
commit 92f04cbcb898880be205feee0795f2f3cb92b260
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date:   Wed Aug 15 21:04:15 2012 +0300

    inputs: add spice_inputs_key_press_and_release()
    
    If the server is capable of SPICE_INPUTS_CAP_SCANCODE, then we send
    can send a single message with key press and release, to avoid
    unwanted guest side key repeatition due to network jitter.
    
    If the server is not capable, spice-gtk will use some compatibility
    mode and send the existing DOWN and UP key events seperately.

diff --git a/doc/reference/spice-gtk-sections.txt b/doc/reference/spice-gtk-sections.txt
index daf575c..60e287a 100644
--- a/doc/reference/spice-gtk-sections.txt
+++ b/doc/reference/spice-gtk-sections.txt
@@ -203,6 +203,7 @@ spice_inputs_position
 spice_inputs_button_press
 spice_inputs_button_release
 spice_inputs_key_press
+spice_inputs_key_press_and_release
 spice_inputs_key_release
 spice_inputs_set_key_locks
 <SUBSECTION Standard>
diff --git a/gtk/channel-inputs.c b/gtk/channel-inputs.c
index 3259da8..0526738 100644
--- a/gtk/channel-inputs.c
+++ b/gtk/channel-inputs.c
@@ -514,6 +514,43 @@ void spice_inputs_key_release(SpiceInputsChannel *channel, guint scancode)
     spice_msg_out_send(msg);
 }
 
+/**
+ * spice_inputs_key_press_and_release:
+ * @channel:
+ * @scancode: a PC AT key scancode
+ *
+ * Press and release a key event atomically (in the same message).
+ *
+ * Since: 0.13
+ **/
+void spice_inputs_key_press_and_release(SpiceInputsChannel *input_channel, guint scancode)
+{
+    SpiceChannel *channel = SPICE_CHANNEL(input_channel);
+
+    g_return_if_fail(channel != NULL);
+    g_return_if_fail(channel->priv->state != SPICE_CHANNEL_STATE_UNCONNECTED);
+
+    if (channel->priv->state != SPICE_CHANNEL_STATE_READY)
+        return;
+    if (spice_channel_get_read_only(channel))
+        return;
+
+    if (spice_channel_test_capability(channel, SPICE_INPUTS_CAP_KEY_SCANCODE)) {
+        SpiceMsgOut *msg;
+        guint16 *code;
+
+        msg = spice_msg_out_new(channel, SPICE_MSGC_INPUTS_KEY_SCANCODE);
+        code = (guint16*)spice_marshaller_reserve_space(msg->marshaller, 2 * sizeof(guint16));
+        *code++ = spice_make_scancode(scancode, FALSE);
+        *code = spice_make_scancode(scancode, TRUE);
+        spice_msg_out_send(msg);
+    } else {
+        SPICE_DEBUG("The server doesn't support atomic press and release");
+        spice_inputs_key_press(input_channel, scancode);
+        spice_inputs_key_release(input_channel, scancode);
+    }
+}
+
 /* main or coroutine context */
 static SpiceMsgOut* set_key_locks(SpiceInputsChannel *channel, guint locks)
 {
diff --git a/gtk/channel-inputs.h b/gtk/channel-inputs.h
index 9968b3b..3179a76 100644
--- a/gtk/channel-inputs.h
+++ b/gtk/channel-inputs.h
@@ -82,6 +82,7 @@ void spice_inputs_button_release(SpiceInputsChannel *channel, gint button,
 void spice_inputs_key_press(SpiceInputsChannel *channel, guint scancode);
 void spice_inputs_key_release(SpiceInputsChannel *channel, guint scancode);
 void spice_inputs_set_key_locks(SpiceInputsChannel *channel, guint locks);
+void spice_inputs_key_press_and_release(SpiceInputsChannel *channel, guint scancode);
 
 G_END_DECLS
 
diff --git a/gtk/map-file b/gtk/map-file
index 0d48bb3..ed2c07f 100644
--- a/gtk/map-file
+++ b/gtk/map-file
@@ -46,6 +46,7 @@ spice_inputs_button_press;
 spice_inputs_button_release;
 spice_inputs_channel_get_type;
 spice_inputs_key_press;
+spice_inputs_key_press_and_release;
 spice_inputs_key_release;
 spice_inputs_lock_get_type;
 spice_inputs_motion;
commit 79a86657a013a2c20fe03e1aa9065bb01a587ed2
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date:   Wed Aug 15 20:45:33 2012 +0300

    util-priv: factor out spice_make_scancode()
    
    Factor out the keyboard scancode manipulation function, to be reusable
    by newer code.

diff --git a/gtk/channel-inputs.c b/gtk/channel-inputs.c
index 02ac026..3259da8 100644
--- a/gtk/channel-inputs.c
+++ b/gtk/channel-inputs.c
@@ -483,15 +483,8 @@ void spice_inputs_key_press(SpiceInputsChannel *channel, guint scancode)
     if (spice_channel_get_read_only(SPICE_CHANNEL(channel)))
         return;
 
-    SPICE_DEBUG("%s: scancode %d", __FUNCTION__, scancode);
-    if (scancode < 0x100) {
-        down.code = scancode;
-    } else {
-        down.code = 0xe0 | ((scancode - 0x100) << 8);
-    }
-
-    msg = spice_msg_out_new(SPICE_CHANNEL(channel),
-                            SPICE_MSGC_INPUTS_KEY_DOWN);
+    down.code = spice_make_scancode(scancode, FALSE);
+    msg = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_INPUTS_KEY_DOWN);
     msg->marshallers->msgc_inputs_key_down(msg->marshaller, &down);
     spice_msg_out_send(msg);
 }
@@ -515,15 +508,8 @@ void spice_inputs_key_release(SpiceInputsChannel *channel, guint scancode)
     if (spice_channel_get_read_only(SPICE_CHANNEL(channel)))
         return;
 
-    SPICE_DEBUG("%s: scancode %d", __FUNCTION__, scancode);
-    if (scancode < 0x100) {
-        up.code = scancode | 0x80;
-    } else {
-        up.code = 0x80e0 | ((scancode - 0x100) << 8);
-    }
-
-    msg = spice_msg_out_new(SPICE_CHANNEL(channel),
-                            SPICE_MSGC_INPUTS_KEY_UP);
+    up.code = spice_make_scancode(scancode, TRUE);
+    msg = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_INPUTS_KEY_UP);
     msg->marshallers->msgc_inputs_key_up(msg->marshaller, &up);
     spice_msg_out_send(msg);
 }
diff --git a/gtk/spice-util-priv.h b/gtk/spice-util-priv.h
index c2022a3..ee5a42d 100644
--- a/gtk/spice-util-priv.h
+++ b/gtk/spice-util-priv.h
@@ -28,6 +28,7 @@ G_BEGIN_DECLS
 gboolean spice_strv_contains(const GStrv strv, const gchar *str);
 gchar* spice_uuid_to_string(const guint8 uuid[16]);
 const gchar* spice_yes_no(gboolean value);
+guint16 spice_make_scancode(guint scancode, gboolean release);
 
 #if GLIB_CHECK_VERSION(2,32,0)
 #define STATIC_MUTEX            GMutex
diff --git a/gtk/spice-util.c b/gtk/spice-util.c
index 8f4f1dc..774a145 100644
--- a/gtk/spice-util.c
+++ b/gtk/spice-util.c
@@ -224,3 +224,24 @@ const gchar* spice_yes_no(gboolean value)
 {
     return value ? "yes" : "no";
 }
+
+G_GNUC_INTERNAL
+guint16 spice_make_scancode(guint scancode, gboolean release)
+{
+    SPICE_DEBUG("%s: %s scancode %d",
+                __FUNCTION__, release ? "release" : "", scancode);
+
+    if (release) {
+        if (scancode < 0x100)
+            return scancode | 0x80;
+        else
+            return 0x80e0 | ((scancode - 0x100) << 8);
+    } else {
+        if (scancode < 0x100)
+            return scancode;
+        else
+            return 0xe0 | ((scancode - 0x100) << 8);
+    }
+
+    g_return_val_if_reached(0);
+}
commit ecdbd660ec88c6177de064b4a4e302f0348dbb4b
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date:   Tue Aug 28 18:45:49 2012 +0200

    Update spice-common

diff --git a/spice-common b/spice-common
index 2c55c9d..86e286b 160000
--- a/spice-common
+++ b/spice-common
@@ -1 +1 @@
-Subproject commit 2c55c9d0c6a5752a16f5e22667196b461c85d4c8
+Subproject commit 86e286ba2003c206e700fd70ec67c1cf4ac8d8a6
commit 8c9834390435058760446e635a969518da0175f8
Author: Yonit Halperin <yhalperi at redhat.com>
Date:   Sat Aug 25 23:20:24 2012 +0300

    channel-smartcard: do not attach temporary migration channel to smartcard
    
    During migration, the smartcard channel that belongs to the temporary
    copied session shouldn't be active.

diff --git a/gtk/channel-main.c b/gtk/channel-main.c
index 7e478af..ff9b0c1 100644
--- a/gtk/channel-main.c
+++ b/gtk/channel-main.c
@@ -1701,6 +1701,7 @@ static gboolean migrate_connect(gpointer data)
 
     session = spice_channel_get_session(mig->src_channel);
     mig->session = spice_session_new_from_session(session);
+    mig->session->priv->migration_copy = true;
 
     if ((c->peer_hdr.major_version == 1) &&
         (c->peer_hdr.minor_version < 1)) {
diff --git a/gtk/channel-smartcard.c b/gtk/channel-smartcard.c
index 6fa4435..41d9554 100644
--- a/gtk/channel-smartcard.c
+++ b/gtk/channel-smartcard.c
@@ -27,6 +27,7 @@
 #include "spice-channel-priv.h"
 #include "smartcard-manager.h"
 #include "smartcard-manager-priv.h"
+#include "spice-session-priv.h"
 
 /**
  * SECTION:channel-smartcard
@@ -120,28 +121,39 @@ static void spice_smartcard_channel_init(SpiceSmartcardChannel *channel)
     priv->message_queue = g_queue_new();
 
 #ifdef USE_SMARTCARD
-    SpiceSmartcardManager *manager;
-
-    manager = spice_smartcard_manager_get();
-
     priv->pending_card_insertions =
         g_hash_table_new_full(g_direct_hash, g_direct_equal,
                               (GDestroyNotify)vreader_free, NULL);
     priv->pending_reader_removals =
          g_hash_table_new_full(g_direct_hash, g_direct_equal,
                                (GDestroyNotify)vreader_free, NULL);
-
-    g_signal_connect(G_OBJECT(manager), "reader-added",
-                     (GCallback)reader_added_cb, channel);
-    g_signal_connect(G_OBJECT(manager), "reader-removed",
-                     (GCallback)reader_removed_cb, channel);
-    g_signal_connect(G_OBJECT(manager), "card-inserted",
-                     (GCallback)card_inserted_cb, channel);
-    g_signal_connect(G_OBJECT(manager), "card-removed",
-                     (GCallback)card_removed_cb, channel);
 #endif
 }
 
+static void spice_smartcard_channel_constructed(GObject *object)
+{
+    SpiceSession *s = spice_channel_get_session(SPICE_CHANNEL(object));
+    SpiceSmartcardChannel *channel = SPICE_SMARTCARD_CHANNEL(object);
+    SpiceSmartcardManager *manager = spice_smartcard_manager_get();
+
+
+    g_return_if_fail(s != NULL);
+    if (!s->priv->migration_copy) {
+        g_signal_connect(G_OBJECT(manager), "reader-added",
+                         (GCallback)reader_added_cb, channel);
+        g_signal_connect(G_OBJECT(manager), "reader-removed",
+                         (GCallback)reader_removed_cb, channel);
+        g_signal_connect(G_OBJECT(manager), "card-inserted",
+                         (GCallback)card_inserted_cb, channel);
+        g_signal_connect(G_OBJECT(manager), "card-removed",
+                         (GCallback)card_removed_cb, channel);
+    }
+
+    if (G_OBJECT_CLASS(spice_smartcard_channel_parent_class)->constructed)
+        G_OBJECT_CLASS(spice_smartcard_channel_parent_class)->constructed(object);
+
+}
+
 static void spice_smartcard_channel_finalize(GObject *obj)
 {
     SpiceSmartcardChannelPrivate *c = SPICE_SMARTCARD_CHANNEL_GET_PRIVATE(obj);
@@ -200,6 +212,8 @@ static void spice_smartcard_channel_class_init(SpiceSmartcardChannelClass *klass
     SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
 
     gobject_class->finalize     = spice_smartcard_channel_finalize;
+    gobject_class->constructed  = spice_smartcard_channel_constructed;
+
     channel_class->handle_msg   = spice_smartcard_handle_msg;
     channel_class->channel_up   = spice_smartcard_channel_up;
     channel_class->channel_reset = spice_smartcard_channel_reset;
@@ -448,16 +462,19 @@ static void spice_smartcard_channel_up_cb(GObject *source_object,
                                           gpointer user_data)
 {
     SpiceChannel *channel = SPICE_CHANNEL(user_data);
-    GError *error = NULL;
 
     g_return_if_fail(channel != NULL);
     g_return_if_fail(SPICE_IS_SESSION(source_object));
 
-    spice_smartcard_manager_init_finish(SPICE_SESSION(source_object),
-                                        res, &error);
-    if (error)
-        g_warning("%s", error->message);
-    g_clear_error(&error);
+    if (!spice_channel_get_session(SPICE_CHANNEL(channel))->priv->migration_copy) {
+        GError *error = NULL;
+
+        spice_smartcard_manager_init_finish(SPICE_SESSION(source_object),
+                                            res, &error);
+        if (error)
+            g_warning("%s", error->message);
+        g_clear_error(&error);
+    }
 }
 
 static void spice_smartcard_channel_up(SpiceChannel *channel)
diff --git a/gtk/spice-session-priv.h b/gtk/spice-session-priv.h
index e6cd17f..13345b4 100644
--- a/gtk/spice-session-priv.h
+++ b/gtk/spice-session-priv.h
@@ -86,6 +86,7 @@ struct _SpiceSessionPrivate {
     gboolean          disconnecting;
     gboolean          migrate_wait_init;
     guint             after_main_init;
+    gboolean          migration_copy;
 
     display_cache     images;
     display_cache     palettes;
commit 156ef301a0b65a843f648217b90f0dd10d7cb39e
Author: Yonit Halperin <yhalperi at redhat.com>
Date:   Sat Aug 25 23:20:23 2012 +0300

    migration: copy enable-smartcard/audio/usbredir state to the migrated session
    
    Otherwise, we will not create smartcard/usb channel on the destination
    side, and we will create audio channels, no matter if they existed
    of didn't exist for the src side.

diff --git a/gtk/spice-channel.c b/gtk/spice-channel.c
index a557ce8..7e23bfe 100644
--- a/gtk/spice-channel.c
+++ b/gtk/spice-channel.c
@@ -1912,9 +1912,7 @@ SpiceChannel *spice_channel_new(SpiceSession *s, int type, int id)
         break;
     case SPICE_CHANNEL_PLAYBACK:
     case SPICE_CHANNEL_RECORD: {
-        gboolean enabled;
-        g_object_get(G_OBJECT(s), "enable-audio", &enabled, NULL);
-        if (!enabled) {
+        if (!s->priv->audio) {
             g_debug("audio channel is disabled, not creating it");
             return NULL;
         }
@@ -1924,9 +1922,7 @@ SpiceChannel *spice_channel_new(SpiceSession *s, int type, int id)
     }
 #ifdef USE_SMARTCARD
     case SPICE_CHANNEL_SMARTCARD: {
-        gboolean enabled;
-        g_object_get(G_OBJECT(s), "enable-smartcard", &enabled, NULL);
-        if (!enabled) {
+        if (!s->priv->smartcard) {
             g_debug("smartcard channel is disabled, not creating it");
             return NULL;
         }
@@ -1936,9 +1932,7 @@ SpiceChannel *spice_channel_new(SpiceSession *s, int type, int id)
 #endif
 #ifdef USE_USBREDIR
     case SPICE_CHANNEL_USBREDIR: {
-        gboolean enabled;
-        g_object_get(G_OBJECT(s), "enable-usbredir", &enabled, NULL);
-        if (!enabled) {
+        if (!s->priv->usbredir) {
             g_debug("usbredir channel is disabled, not creating it");
             return NULL;
         }
diff --git a/gtk/spice-session.c b/gtk/spice-session.c
index f714676..b6c7642 100644
--- a/gtk/spice-session.c
+++ b/gtk/spice-session.c
@@ -1089,6 +1089,9 @@ SpiceSession *spice_session_new_from_session(SpiceSession *session)
                  "verify", &c->verify,
                  "smartcard-certificates", &c->smartcard_certificates,
                  "smartcard-db", &c->smartcard_db,
+                 "enable-smartcard", &c->smartcard,
+                 "enable-audio", &c->audio,
+                 "enable-usbredir", &c->usbredir,
                  NULL);
 
     c->client_provided_sockets = s->client_provided_sockets;
diff --git a/gtk/usb-device-manager.c b/gtk/usb-device-manager.c
index bdef24e..6358da8 100644
--- a/gtk/usb-device-manager.c
+++ b/gtk/usb-device-manager.c
@@ -1215,14 +1215,12 @@ spice_usb_device_manager_can_redirect_device(SpiceUsbDeviceManager  *self,
     const struct usbredirfilter_rule *guest_filter_rules = NULL;
     SpiceUsbDeviceManagerPrivate *priv = self->priv;
     int i, guest_filter_rules_count;
-    gboolean enabled;
 
     g_return_val_if_fail(SPICE_IS_USB_DEVICE_MANAGER(self), FALSE);
     g_return_val_if_fail(device != NULL, FALSE);
     g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
 
-    g_object_get(G_OBJECT(priv->session), "enable-usbredir", &enabled, NULL);
-    if (!enabled) {
+    if (!priv->session->priv->usbredir) {
         g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
                             _("USB redirection is disabled"));
         return FALSE;
commit b676434c98d5236745b5797c3e56cc45bd7b1348
Author: Yonit Halperin <yhalperi at redhat.com>
Date:   Sat Aug 25 23:20:22 2012 +0300

    seamless migration: don't reset messages data when swapping channels
    
    When swapping the src and dest channels's, we need to keep
    the xmit_queue and msg serials. Their state is expected to stay the same
    after migration.

diff --git a/gtk/channel-main.c b/gtk/channel-main.c
index 0e073a6..7e478af 100644
--- a/gtk/channel-main.c
+++ b/gtk/channel-main.c
@@ -1796,7 +1796,9 @@ static void main_migrate_connect(SpiceChannel *channel,
             SPICE_DEBUG("migration (semi-seamless): connections all ok");
             reply_type = SPICE_MSGC_MAIN_MIGRATE_CONNECTED;
         }
-        spice_session_set_migration(spice_channel_get_session(channel), mig.session);
+        spice_session_set_migration(spice_channel_get_session(channel),
+                                    mig.session,
+                                    mig.do_seamless);
     }
     g_object_unref(mig.session);
 
diff --git a/gtk/spice-channel-priv.h b/gtk/spice-channel-priv.h
index 8ed79fa..c01b3c4 100644
--- a/gtk/spice-channel-priv.h
+++ b/gtk/spice-channel-priv.h
@@ -174,7 +174,7 @@ void spice_channel_handle_migrate(SpiceChannel *channel, SpiceMsgIn *in);
 
 gint spice_channel_get_channel_id(SpiceChannel *channel);
 gint spice_channel_get_channel_type(SpiceChannel *channel);
-void spice_channel_swap(SpiceChannel *channel, SpiceChannel *swap);
+void spice_channel_swap(SpiceChannel *channel, SpiceChannel *swap, gboolean swap_msgs);
 gboolean spice_channel_get_read_only(SpiceChannel *channel);
 void spice_channel_reset(SpiceChannel *channel, gboolean migrating);
 
diff --git a/gtk/spice-channel.c b/gtk/spice-channel.c
index 5f37cbc..a557ce8 100644
--- a/gtk/spice-channel.c
+++ b/gtk/spice-channel.c
@@ -2591,7 +2591,7 @@ enum spice_channel_state spice_channel_get_state(SpiceChannel *channel)
 }
 
 G_GNUC_INTERNAL
-void spice_channel_swap(SpiceChannel *channel, SpiceChannel *swap)
+void spice_channel_swap(SpiceChannel *channel, SpiceChannel *swap, gboolean swap_msgs)
 {
     SpiceChannelPrivate *c = SPICE_CHANNEL_GET_PRIVATE(channel);
     SpiceChannelPrivate *s = SPICE_CHANNEL_GET_PRIVATE(swap);
@@ -2614,11 +2614,13 @@ void spice_channel_swap(SpiceChannel *channel, SpiceChannel *swap)
     SWAP(ctx);
     SWAP(ssl);
     SWAP(sslverify);
-    SWAP(in_serial);
-    SWAP(out_serial);
     SWAP(use_mini_header);
-    SWAP(xmit_queue);
-    SWAP(xmit_queue_blocked);
+    if (swap_msgs) {
+        SWAP(xmit_queue);
+        SWAP(xmit_queue_blocked);
+        SWAP(in_serial);
+        SWAP(out_serial);
+    }
     SWAP(caps);
     SWAP(common_caps);
     SWAP(remote_caps);
diff --git a/gtk/spice-session-priv.h b/gtk/spice-session-priv.h
index c24ef8e..e6cd17f 100644
--- a/gtk/spice-session-priv.h
+++ b/gtk/spice-session-priv.h
@@ -82,6 +82,7 @@ struct _SpiceSessionPrivate {
     SpiceSession      *migration;
     GList             *migration_left;
     SpiceSessionMigration migration_state;
+    gboolean          full_migration; /* seamless migration indicator */
     gboolean          disconnecting;
     gboolean          migrate_wait_init;
     guint             after_main_init;
@@ -119,7 +120,9 @@ void spice_session_set_mm_time(SpiceSession *session, guint32 time);
 guint32 spice_session_get_mm_time(SpiceSession *session);
 
 void spice_session_switching_disconnect(SpiceSession *session);
-void spice_session_set_migration(SpiceSession *session, SpiceSession *migration);
+void spice_session_set_migration(SpiceSession *session,
+                                 SpiceSession *migration,
+                                 gboolean full_migration);
 void spice_session_abort_migration(SpiceSession *session);
 void spice_session_set_migration_state(SpiceSession *session, SpiceSessionMigration state);
 
diff --git a/gtk/spice-session.c b/gtk/spice-session.c
index 995b2ed..f714676 100644
--- a/gtk/spice-session.c
+++ b/gtk/spice-session.c
@@ -1194,7 +1194,9 @@ void spice_session_switching_disconnect(SpiceSession *self)
 }
 
 G_GNUC_INTERNAL
-void spice_session_set_migration(SpiceSession *session, SpiceSession *migration)
+void spice_session_set_migration(SpiceSession *session,
+                                 SpiceSession *migration,
+                                 gboolean full_migration)
 {
     SpiceSessionPrivate *s = SPICE_SESSION_GET_PRIVATE(session);
     SpiceSessionPrivate *m = SPICE_SESSION_GET_PRIVATE(migration);
@@ -1202,6 +1204,7 @@ void spice_session_set_migration(SpiceSession *session, SpiceSession *migration)
 
     g_return_if_fail(s != NULL);
 
+    s->full_migration = full_migration;
     spice_session_set_migration_state(session, SPICE_SESSION_MIGRATION_MIGRATING);
 
     g_warn_if_fail(s->migration == NULL);
@@ -1278,7 +1281,8 @@ void spice_session_abort_migration(SpiceSession *session)
         spice_channel_swap(c->channel,
             spice_session_lookup_channel(s->migration,
                                          spice_channel_get_channel_id(c->channel),
-                                         spice_channel_get_channel_type(c->channel)));
+                                         spice_channel_get_channel_type(c->channel)),
+                                         !s->full_migration);
     }
 
     g_list_free(s->migration_left);
@@ -1308,7 +1312,10 @@ void spice_session_channel_migrate(SpiceSession *session, SpiceChannel *channel)
     c = spice_session_lookup_channel(s->migration, id, type);
     g_return_if_fail(c != NULL);
 
-    spice_channel_swap(channel, c);
+    if (!g_queue_is_empty(&c->priv->xmit_queue) && s->full_migration) {
+        SPICE_DEBUG("mig channel xmit queue is not empty. type %s", c->priv->name);
+    }
+    spice_channel_swap(channel, c, !s->full_migration);
     s->migration_left = g_list_remove(s->migration_left, channel);
 
     if (g_list_length(s->migration_left) == 0) {
commit 202cdd9d8a8e0a08326c44c0ad75997f026bda84
Author: Yonit Halperin <yhalperi at redhat.com>
Date:   Sat Aug 25 23:20:21 2012 +0300

    seamless migration: transfer pending msgs to the destination, instead of sending them to the src before FLUSH_MARK
    
    In order to save migration time, and probably also decrease migration
    data size, we push the flush mark to the src server before any other
    message. All the other pending msgs will be sent later to the
    destination server (see next patch).

diff --git a/gtk/channel-base.c b/gtk/channel-base.c
index 2968f42..fa39761 100644
--- a/gtk/channel-base.c
+++ b/gtk/channel-base.c
@@ -157,12 +157,14 @@ void spice_channel_handle_migrate(SpiceChannel *channel, SpiceMsgIn *in)
 
     SPICE_DEBUG("%s: channel %s flags %u", __FUNCTION__, c->name, mig->flags);
     if (mig->flags & SPICE_MIGRATE_NEED_FLUSH) {
-        /* iterate_write is blocking and flushing all pending write */
-        SPICE_CHANNEL_GET_CLASS(channel)->iterate_write(channel);
-
+        /* if peer version > 1: pushing the mark msg before all other messgages and sending it,
+         * and only it */
+        if (c->peer_hdr.major_version == 1) {
+            /* iterate_write is blocking and flushing all pending write */
+            SPICE_CHANNEL_GET_CLASS(channel)->iterate_write(channel);
+        }
         out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MIGRATE_FLUSH_MARK);
         spice_msg_out_send_internal(out);
-        SPICE_CHANNEL_GET_CLASS(channel)->iterate_write(channel);
     }
     if (mig->flags & SPICE_MIGRATE_NEED_DATA_TRANSFER) {
         spice_channel_recv_msg(channel, get_msg_handler, &data);
@@ -175,8 +177,10 @@ void spice_channel_handle_migrate(SpiceChannel *channel, SpiceMsgIn *in)
         }
     }
 
+    /* swapping channels sockets */
     spice_session_channel_migrate(c->session, channel);
 
+    /* pushing the MIGRATE_DATA before all other pending messages */
     if ((mig->flags & SPICE_MIGRATE_NEED_DATA_TRANSFER) && (data != NULL)) {
         out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MIGRATE_DATA);
         spice_marshaller_add(out->marshaller, data->data,
commit c22a53f4ebd078c37ef014a160ce30b604b55582
Author: Yonit Halperin <yhalperi at redhat.com>
Date:   Sat Aug 25 23:20:20 2012 +0300

    seamless migration: src and dest servers handshake
    
    Flow:
    (1) *src* main channel coroutine (main_handle_migrate_begin_seamless):
        handles SPICE_MSG_MAIN_MIGRATE_BEGIN_SEAMLESS; yields to the main loop,
        supplying it the destination information needed for connection.
    (2) main context (migrate_connect):
        Establishes a new session for connecting to the destination.
        After all the channels are opened (async), their state, except for
        the one of the main channel, is modified to
        SPICE_CHANNEL_STATE_MIGRATING (see migrate_channel_event_cb);
        no reading is done from the channel during this state.
        The dest main channel's state is changed to SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE
    
    (3) *dest* main channel coroutine: sends to the dest server SPICE_MSGC_MAIN_MIGRATE_DST_DO_SEAMLESS
        (see spice_channel_recv_auth)
    (4) *dest* main channel coroutine: recevices SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_ACK/NACK.
         adds main_migrate_handshake_done to the main loop.
    (5) main context: when all the dest session channels are connected, and the main channel handshake
        is done, we yield to the src main channel coroutine (see
        migrate_channel_event_cb and main_migrate_handshake_done)
    (6) *src* main channel coroutine: sends to the src server
        SPICE_MSGC_MAIN_MIGRATE_(CONNECTED|CONNECTED_SEAMLESS|CONNECT_ERROR)
    
    For more details see spice-protocol. commit
    1ad5d259cb4b695ec3106de7ccd082e031e7ae11

diff --git a/gtk/channel-main.c b/gtk/channel-main.c
index bb0e361..0e073a6 100644
--- a/gtk/channel-main.c
+++ b/gtk/channel-main.c
@@ -49,6 +49,8 @@
 
 #define MAX_DISPLAY 16
 
+typedef struct spice_migrate spice_migrate;
+
 struct _SpiceMainChannelPrivate  {
     enum SpiceMouseMode         mouse_mode;
     bool                        agent_connected;
@@ -80,16 +82,22 @@ struct _SpiceMainChannelPrivate  {
 
     guint                       switch_host_delayed_id;
     guint                       migrate_delayed_id;
+    spice_migrate               *migrate_data;
 };
 
-typedef struct spice_migrate spice_migrate;
-
 struct spice_migrate {
     struct coroutine *from;
     SpiceMigrationDstInfo *info;
     SpiceSession *session;
     guint nchannels;
-    SpiceChannel *channel;
+    SpiceChannel *src_channel;
+    SpiceChannel *dst_channel;
+    bool do_seamless; /* used as input and output for the seamless migration handshake.
+                         input: whether to send to the dest SPICE_MSGC_MAIN_MIGRATE_DST_DO_SEAMLESS
+                         output: whether the dest approved seamless migration
+                         (SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_ACK/NACK)
+                       */
+    uint32_t src_mig_version;
 };
 
 G_DEFINE_TYPE(SpiceMainChannel, spice_main_channel, SPICE_TYPE_CHANNEL)
@@ -131,6 +139,8 @@ static void agent_send_msg_queue(SpiceMainChannel *channel);
 static void agent_free_msg_queue(SpiceMainChannel *channel);
 static void migrate_channel_event_cb(SpiceChannel *channel, SpiceChannelEvent event,
                                      gpointer data);
+static gboolean main_migrate_handshake_done(gpointer data);
+static void spice_main_channel_send_migration_handshake(SpiceChannel *channel);
 
 /* ------------------------------------------------------------------ */
 
@@ -164,6 +174,7 @@ static void spice_main_channel_reset_capabilties(SpiceChannel *channel)
     spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE);
     spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_MAIN_CAP_NAME_AND_UUID);
     spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_MAIN_CAP_AGENT_CONNECTED_TOKENS);
+    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_MAIN_CAP_SEAMLESS_MIGRATE);
 }
 
 static void spice_main_channel_init(SpiceMainChannel *channel)
@@ -327,6 +338,7 @@ static void spice_main_channel_class_init(SpiceMainChannelClass *klass)
     channel_class->iterate_write = spice_channel_iterate_write;
     channel_class->channel_reset = spice_main_channel_reset;
     channel_class->channel_reset_capabilities = spice_main_channel_reset_capabilties;
+    channel_class->channel_send_migration_handshake = spice_main_channel_send_migration_handshake;
 
     /**
      * SpiceMainChannel:mouse-mode:
@@ -658,7 +670,6 @@ static void spice_main_channel_class_init(SpiceMainChannelClass *klass)
                      1,
                      G_TYPE_OBJECT);
 
-
     g_type_class_add_private(klass, sizeof(SpiceMainChannelPrivate));
 }
 
@@ -1573,6 +1584,26 @@ static SpiceChannel* migrate_channel_connect(spice_migrate *mig, int type, int i
     return newc;
 }
 
+/* coroutine context */
+static void spice_main_channel_send_migration_handshake(SpiceChannel *channel)
+{
+    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
+
+    if (!spice_channel_test_capability(channel, SPICE_MAIN_CAP_SEAMLESS_MIGRATE)) {
+        c->migrate_data->do_seamless = false;
+        g_idle_add(main_migrate_handshake_done, c->migrate_data);
+    } else {
+        SpiceMsgcMainMigrateDstDoSeamless msg_data;
+        SpiceMsgOut *msg_out;
+
+        msg_data.src_version = c->migrate_data->src_mig_version;
+
+        msg_out = spice_msg_out_new(channel, SPICE_MSGC_MAIN_MIGRATE_DST_DO_SEAMLESS);
+        msg_out->marshallers->msgc_main_migrate_dst_do_seamless(msg_out->marshaller, &msg_data);
+        spice_msg_out_send_internal(msg_out);
+    }
+}
+
 /* main context */
 static void migrate_channel_event_cb(SpiceChannel *channel, SpiceChannelEvent event,
                                      gpointer data)
@@ -1584,13 +1615,22 @@ static void migrate_channel_event_cb(SpiceChannel *channel, SpiceChannelEvent ev
     g_return_if_fail(mig->nchannels > 0);
     g_signal_handlers_disconnect_by_func(channel, migrate_channel_event_cb, data);
 
-    session = spice_channel_get_session(mig->channel);
+    session = spice_channel_get_session(mig->src_channel);
 
     switch (event) {
     case SPICE_CHANNEL_OPENED:
-        c->state = SPICE_CHANNEL_STATE_MIGRATING;
 
         if (c->channel_type == SPICE_CHANNEL_MAIN) {
+            if (mig->do_seamless) {
+                SpiceMainChannelPrivate *main_priv = SPICE_MAIN_CHANNEL(channel)->priv;
+
+                c->state = SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE;
+                mig->dst_channel = channel;
+                main_priv->migrate_data = mig;
+            } else {
+                c->state = SPICE_CHANNEL_STATE_MIGRATING;
+                mig->nchannels--;
+            }
             /* now connect the rest of the channels */
             GList *channels, *l;
             l = channels = spice_session_get_channels(session);
@@ -1602,9 +1642,11 @@ static void migrate_channel_event_cb(SpiceChannel *channel, SpiceChannelEvent ev
                 migrate_channel_connect(mig, curc->channel_type, curc->channel_id);
             }
             g_list_free(channels);
+        } else {
+            c->state = SPICE_CHANNEL_STATE_MIGRATING;
+            mig->nchannels--;
         }
 
-        mig->nchannels--;
         SPICE_DEBUG("migration: channel opened chan:%p, left %d", channel, mig->nchannels);
         if (mig->nchannels == 0)
             coroutine_yieldto(mig->from, NULL);
@@ -1616,6 +1658,22 @@ static void migrate_channel_event_cb(SpiceChannel *channel, SpiceChannelEvent ev
     }
 }
 
+/* main context */
+static gboolean main_migrate_handshake_done(gpointer data)
+{
+    spice_migrate *mig = data;
+    SpiceChannelPrivate  *c = SPICE_CHANNEL(mig->dst_channel)->priv;
+
+    g_return_val_if_fail(c->channel_type == SPICE_CHANNEL_MAIN, FALSE);
+    g_return_val_if_fail(c->state == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE, FALSE);
+
+    c->state = SPICE_CHANNEL_STATE_MIGRATING;
+    mig->nchannels--;
+    if (mig->nchannels == 0)
+        coroutine_yieldto(mig->from, NULL);
+    return FALSE;
+}
+
 #ifdef __GNUC__
 typedef struct __attribute__ ((__packed__)) OldRedMigrationBegin {
 #else
@@ -1638,10 +1696,10 @@ static gboolean migrate_connect(gpointer data)
     g_return_val_if_fail(mig != NULL, FALSE);
     g_return_val_if_fail(mig->info != NULL, FALSE);
     g_return_val_if_fail(mig->nchannels == 0, FALSE);
-    c = SPICE_CHANNEL(mig->channel)->priv;
+    c = SPICE_CHANNEL(mig->src_channel)->priv;
     g_return_val_if_fail(c != NULL, FALSE);
 
-    session = spice_channel_get_session(mig->channel);
+    session = spice_channel_get_session(mig->src_channel);
     mig->session = spice_session_new_from_session(session);
 
     if ((c->peer_hdr.major_version == 1) &&
@@ -1692,7 +1750,7 @@ static gboolean migrate_connect(gpointer data)
     g_signal_connect(mig->session, "channel-new",
                      G_CALLBACK(migrate_channel_new_cb), mig);
 
-    g_signal_emit(mig->channel, signals[SPICE_MIGRATION_STARTED], 0,
+    g_signal_emit(mig->src_channel, signals[SPICE_MIGRATION_STARTED], 0,
                   mig->session);
 
     /* the migration process is in 2 steps, first the main channel and
@@ -1703,16 +1761,22 @@ static gboolean migrate_connect(gpointer data)
 }
 
 /* coroutine context */
-static void main_handle_migrate_begin(SpiceChannel *channel, SpiceMsgIn *in)
+static void main_migrate_connect(SpiceChannel *channel,
+                                 SpiceMigrationDstInfo *dst_info, bool do_seamless,
+                                 uint32_t src_mig_version)
 {
-    SpiceMsgMainMigrationBegin *msg = spice_msg_in_parsed(in);
+    SpiceMainChannelPrivate *main_priv = SPICE_MAIN_CHANNEL(channel)->priv;
     spice_migrate mig = { 0, };
     SpiceMsgOut *out;
     int reply_type;
 
-    mig.channel = channel;
-    mig.info = &msg->dst_info;
+    mig.src_channel = channel;
+    mig.info = dst_info;
     mig.from = coroutine_self();
+    mig.do_seamless = do_seamless;
+    mig.src_mig_version = src_mig_version;
+
+    main_priv->migrate_data = &mig;
 
     /* no need to track idle, call is sync for this coroutine */
     g_idle_add(migrate_connect, &mig);
@@ -1725,8 +1789,13 @@ static void main_handle_migrate_begin(SpiceChannel *channel, SpiceMsgIn *in)
         reply_type = SPICE_MSGC_MAIN_MIGRATE_CONNECT_ERROR;
         spice_session_disconnect(mig.session);
     } else {
-        SPICE_DEBUG("migration: connections all ok");
-        reply_type = SPICE_MSGC_MAIN_MIGRATE_CONNECTED;
+        if (mig.do_seamless) {
+            SPICE_DEBUG("migration (seamless): connections all ok");
+            reply_type = SPICE_MSGC_MAIN_MIGRATE_CONNECTED_SEAMLESS;
+        } else {
+            SPICE_DEBUG("migration (semi-seamless): connections all ok");
+            reply_type = SPICE_MSGC_MAIN_MIGRATE_CONNECTED;
+        }
         spice_session_set_migration(spice_channel_get_session(channel), mig.session);
     }
     g_object_unref(mig.session);
@@ -1735,6 +1804,42 @@ static void main_handle_migrate_begin(SpiceChannel *channel, SpiceMsgIn *in)
     spice_msg_out_send(out);
 }
 
+/* coroutine context */
+static void main_handle_migrate_begin(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgMainMigrationBegin *msg = spice_msg_in_parsed(in);
+
+    main_migrate_connect(channel, &msg->dst_info, false, 0);
+}
+
+/* coroutine context */
+static void main_handle_migrate_begin_seamless(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgMainMigrateBeginSeamless *msg = spice_msg_in_parsed(in);
+
+    main_migrate_connect(channel, &msg->dst_info, true, msg->src_mig_version);
+}
+
+static void main_handle_migrate_dst_seamless_ack(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceChannelPrivate  *c = SPICE_CHANNEL(channel)->priv;
+    SpiceMainChannelPrivate *main_priv = SPICE_MAIN_CHANNEL(channel)->priv;
+
+    g_return_if_fail(c->state == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE);
+    main_priv->migrate_data->do_seamless = true;
+    g_idle_add(main_migrate_handshake_done, main_priv->migrate_data);
+}
+
+static void main_handle_migrate_dst_seamless_nack(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceChannelPrivate  *c = SPICE_CHANNEL(channel)->priv;
+    SpiceMainChannelPrivate *main_priv = SPICE_MAIN_CHANNEL(channel)->priv;
+
+    g_return_if_fail(c->state == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE);
+    main_priv->migrate_data->do_seamless = false;
+    g_idle_add(main_migrate_handshake_done, main_priv->migrate_data);
+}
+
 /* main context */
 static gboolean migrate_delayed(gpointer data)
 {
@@ -1848,7 +1953,10 @@ static const spice_msg_handler main_handlers[] = {
     [ SPICE_MSG_MAIN_MIGRATE_END ]         = main_handle_migrate_end,
     [ SPICE_MSG_MAIN_MIGRATE_CANCEL ]      = main_handle_migrate_cancel,
     [ SPICE_MSG_MAIN_MIGRATE_SWITCH_HOST ] = main_handle_migrate_switch_host,
-    [ SPICE_MSG_MAIN_AGENT_CONNECTED_TOKENS ] = main_handle_agent_connected_tokens,
+    [ SPICE_MSG_MAIN_AGENT_CONNECTED_TOKENS ]   = main_handle_agent_connected_tokens,
+    [ SPICE_MSG_MAIN_MIGRATE_BEGIN_SEAMLESS ]   = main_handle_migrate_begin_seamless,
+    [ SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_ACK]  = main_handle_migrate_dst_seamless_ack,
+    [ SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_NACK] = main_handle_migrate_dst_seamless_nack,
 };
 
 /* coroutine context */
@@ -1856,11 +1964,21 @@ static void spice_main_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg)
 {
     int type = spice_msg_in_type(msg);
     SpiceChannelClass *parent_class;
+    SpiceChannelPrivate *c = SPICE_CHANNEL(channel)->priv;
 
     g_return_if_fail(type < SPICE_N_ELEMENTS(main_handlers));
 
     parent_class = SPICE_CHANNEL_CLASS(spice_main_channel_parent_class);
 
+    if (c->state == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE) {
+        if (type != SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_ACK &&
+            type != SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_NACK) {
+            g_critical("unexpected msg (%d)."
+                       "Only MIGRATE_DST_SEAMLESS_ACK/NACK are allowed", type);
+            return;
+        }
+    }
+
     if (main_handlers[type] != NULL)
         main_handlers[type](channel, msg);
     else if (parent_class->handle_msg)
diff --git a/gtk/spice-channel-priv.h b/gtk/spice-channel-priv.h
index 4f094d8..8ed79fa 100644
--- a/gtk/spice-channel-priv.h
+++ b/gtk/spice-channel-priv.h
@@ -71,6 +71,7 @@ enum spice_channel_state {
     SPICE_CHANNEL_STATE_READY,
     SPICE_CHANNEL_STATE_SWITCHING,
     SPICE_CHANNEL_STATE_MIGRATING,
+    SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE,
 };
 
 struct _SpiceChannelPrivate {
diff --git a/gtk/spice-channel.c b/gtk/spice-channel.c
index 54d406d..5f37cbc 100644
--- a/gtk/spice-channel.c
+++ b/gtk/spice-channel.c
@@ -49,6 +49,7 @@ static void spice_channel_send_link(SpiceChannel *channel);
 static void channel_disconnect(SpiceChannel *channel);
 static void channel_reset(SpiceChannel *channel, gboolean migrating);
 static void spice_channel_reset_capabilities(SpiceChannel *channel);
+static void spice_channel_send_migration_handshake(SpiceChannel *channel);
 
 /**
  * SECTION:spice-channel
@@ -1080,6 +1081,10 @@ static void spice_channel_recv_auth(SpiceChannel *channel)
 
     emit_main_context(channel, SPICE_CHANNEL_EVENT, SPICE_CHANNEL_OPENED);
 
+    if (c->state == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE) {
+        spice_channel_send_migration_handshake(channel);
+    }
+
     if (c->state != SPICE_CHANNEL_STATE_MIGRATING)
         spice_channel_up(channel);
 }
@@ -2002,6 +2007,7 @@ static void spice_channel_iterate_read(SpiceChannel *channel)
         spice_channel_recv_auth(channel);
         break;
     case SPICE_CHANNEL_STATE_READY:
+    case SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE:
         spice_channel_recv_msg(channel,
             (handler_msg_in)SPICE_CHANNEL_GET_CLASS(channel)->handle_msg, NULL);
         break;
@@ -2654,3 +2660,14 @@ static void spice_channel_reset_capabilities(SpiceChannel *channel)
         SPICE_CHANNEL_GET_CLASS(channel)->channel_reset_capabilities(channel);
     }
 }
+
+static void spice_channel_send_migration_handshake(SpiceChannel *channel)
+{
+    SpiceChannelPrivate *c = SPICE_CHANNEL_GET_PRIVATE(channel);
+
+    if (SPICE_CHANNEL_GET_CLASS(channel)->channel_send_migration_handshake) {
+        SPICE_CHANNEL_GET_CLASS(channel)->channel_send_migration_handshake(channel);
+    } else {
+        c->state = SPICE_CHANNEL_STATE_MIGRATING;
+    }
+}
diff --git a/gtk/spice-channel.h b/gtk/spice-channel.h
index 30ff1ba..d40b844 100644
--- a/gtk/spice-channel.h
+++ b/gtk/spice-channel.h
@@ -90,11 +90,15 @@ struct _SpiceChannelClass
     void (*channel_reset)(SpiceChannel *channel, gboolean migrating);
     void (*channel_reset_capabilities)(SpiceChannel *channel);
 
+    /*< private >*/
+    /* virtual methods, coroutine context */
+    void (*channel_send_migration_handshake)(SpiceChannel *channel);
+
     /*
      * If adding fields to this struct, remove corresponding
      * amount of padding to avoid changing overall struct size
      */
-    gchar _spice_reserved[SPICE_RESERVED_PADDING];
+    gchar _spice_reserved[SPICE_RESERVED_PADDING - sizeof(void *)];
 };
 
 GType spice_channel_get_type(void);
commit cf5486773d466d84b3f0c3444cfa7500e97cf16c
Author: Yonit Halperin <yhalperi at redhat.com>
Date:   Sat Aug 25 23:20:19 2012 +0300

    seamless-migration: update spice-common submodule
    
    Update channel-main as well to support the change made to
    SpiceMsgMainMigrationBegin: it now holds all the destination fields
    inside SpiceMigrationDstInfo.

diff --git a/gtk/channel-main.c b/gtk/channel-main.c
index 91c9167..bb0e361 100644
--- a/gtk/channel-main.c
+++ b/gtk/channel-main.c
@@ -86,7 +86,7 @@ typedef struct spice_migrate spice_migrate;
 
 struct spice_migrate {
     struct coroutine *from;
-    SpiceMsgMainMigrationBegin *info;
+    SpiceMigrationDstInfo *info;
     SpiceSession *session;
     guint nchannels;
     SpiceChannel *channel;
@@ -1653,7 +1653,7 @@ static gboolean migrate_connect(gpointer data)
         sport = info->sport;
         host = info->host;
     } else {
-        SpiceMsgMainMigrationBegin *info = mig->info;
+        SpiceMigrationDstInfo *info = mig->info;
         SPICE_DEBUG("migrate_begin %d %s %d %d",
                     info->host_size, info->host_data, info->port, info->sport);
         port = info->port;
@@ -1711,7 +1711,7 @@ static void main_handle_migrate_begin(SpiceChannel *channel, SpiceMsgIn *in)
     int reply_type;
 
     mig.channel = channel;
-    mig.info = msg;
+    mig.info = &msg->dst_info;
     mig.from = coroutine_self();
 
     /* no need to track idle, call is sync for this coroutine */
commit a433d9c2b3de42d5a93e41fc3c22aef906579c20
Author: Yonit Halperin <yhalperi at redhat.com>
Date:   Sat Aug 25 23:20:18 2012 +0300

    channel_main: handle SPICE_MSG_AGENT_CONNECTED_TOKENS

diff --git a/gtk/channel-main.c b/gtk/channel-main.c
index 0c15dfa..91c9167 100644
--- a/gtk/channel-main.c
+++ b/gtk/channel-main.c
@@ -163,6 +163,7 @@ static void spice_main_channel_reset_capabilties(SpiceChannel *channel)
 {
     spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE);
     spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_MAIN_CAP_NAME_AND_UUID);
+    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_MAIN_CAP_AGENT_CONNECTED_TOKENS);
 }
 
 static void spice_main_channel_init(SpiceMainChannel *channel)
@@ -1357,6 +1358,16 @@ static void main_handle_agent_connected(SpiceChannel *channel, SpiceMsgIn *in)
 }
 
 /* coroutine context */
+static void main_handle_agent_connected_tokens(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
+    SpiceMsgMainAgentConnectedTokens *msg = spice_msg_in_parsed(in);
+
+    c->agent_tokens = msg->num_tokens;
+    agent_start(SPICE_MAIN_CHANNEL(channel));
+}
+
+/* coroutine context */
 static void main_handle_agent_disconnected(SpiceChannel *channel, SpiceMsgIn *in)
 {
     agent_stopped(SPICE_MAIN_CHANNEL(channel));
@@ -1837,6 +1848,7 @@ static const spice_msg_handler main_handlers[] = {
     [ SPICE_MSG_MAIN_MIGRATE_END ]         = main_handle_migrate_end,
     [ SPICE_MSG_MAIN_MIGRATE_CANCEL ]      = main_handle_migrate_cancel,
     [ SPICE_MSG_MAIN_MIGRATE_SWITCH_HOST ] = main_handle_migrate_switch_host,
+    [ SPICE_MSG_MAIN_AGENT_CONNECTED_TOKENS ] = main_handle_agent_connected_tokens,
 };
 
 /* coroutine context */
commit f645e132a4f0d465cb8ae1e680b5eb61bd98187c
Author: Yonit Halperin <yhalperi at redhat.com>
Date:   Sat Aug 25 23:20:17 2012 +0300

    channel-base: remove bad check of SpiceMsgWaitForChannels validity
    
    SpiceMsgWaitForChannels is not packed. Comparing the original
    msg size to SpiceMsgWaitForChannels is wrong.

diff --git a/gtk/channel-base.c b/gtk/channel-base.c
index cc4d242..2968f42 100644
--- a/gtk/channel-base.c
+++ b/gtk/channel-base.c
@@ -120,9 +120,6 @@ void spice_channel_handle_wait_for_channels(SpiceChannel *channel, SpiceMsgIn *i
     SpiceMsgWaitForChannels *wfc = spice_msg_in_parsed(in);
     int i;
 
-    g_return_if_fail(spice_header_get_msg_size(in->header, channel->priv->use_mini_header) >=
-                     sizeof(*wfc) + wfc->wait_count * sizeof(wfc->wait_list[0]));
-
     for (i = 0; i < wfc->wait_count; ++i) {
         WaitForChannelData data = {
             .wait = wfc->wait_list + i,
commit 87f956ae3010a814473968e0cba6f8d7df5c2cd3
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date:   Tue Aug 28 17:45:12 2012 +0200

    Update spice-common

diff --git a/spice-common b/spice-common
index c2adbb0..2c55c9d 160000
--- a/spice-common
+++ b/spice-common
@@ -1 +1 @@
-Subproject commit c2adbb00dc0b29de0fe297f241fb0efeb4a81510
+Subproject commit 2c55c9d0c6a5752a16f5e22667196b461c85d4c8
commit db1929a9707a9d7c3f0bd08dc78b73bd9911e605
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date:   Tue Aug 28 18:06:21 2012 +0200

    Release v0.13

diff --git a/NEWS b/NEWS
index 83fbe44..cd9a08d 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,15 @@
+v0.13
+=====
+
+- ABI break! SONAME has been bumped, all programs and libraries
+  linking to spice-gtk need to be recompiled against this version
+- Add support for USB device redirection on Windows
+- Add monitors config support (multiple monitors in same display)
+- Inhibit automount on GNOME desktop, to ease USB redirection
+- Better video support (reduce some glitches)
+- Misc migration fixes
+- Various bug fixes and improvements
+
 v0.12
 =====
 
diff --git a/doc/reference/spice-gtk-overrides.txt b/doc/reference/spice-gtk-overrides.txt
new file mode 100644
index 0000000..e69de29


More information about the Spice-commits mailing list