[vinagre] plugins: add spice plugin



commit df39ca2a0b8bcbf6aad39202b433049ecc65e4e3
Author: Marc-André Lureau <marcandre lureau redhat com>
Date:   Sat Feb 19 00:50:17 2011 +0100

    plugins: add spice plugin
    
    Use --enable-spice configure argument.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=642844

 configure.ac                             |   18 +
 data/.gitignore                          |    2 -
 plugins/Makefile.am                      |   35 +-
 plugins/spice/Makefile.am                |   33 ++
 plugins/spice/spice.plugin.desktop.in    |    9 +
 plugins/spice/vinagre-spice-connection.c |  551 ++++++++++++++++++++++
 plugins/spice/vinagre-spice-connection.h |   92 ++++
 plugins/spice/vinagre-spice-plugin.c     |  360 ++++++++++++++
 plugins/spice/vinagre-spice-plugin.h     |   62 +++
 plugins/spice/vinagre-spice-tab.c        |  753 ++++++++++++++++++++++++++++++
 plugins/spice/vinagre-spice-tab.h        |   76 +++
 plugins/spice/vinagre-spice-tunnel.c     |  155 ++++++
 plugins/spice/vinagre-spice-tunnel.h     |   46 ++
 13 files changed, 2177 insertions(+), 15 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 8a5a9f1..82f5145 100644
--- a/configure.ac
+++ b/configure.ac
@@ -75,6 +75,7 @@ GLIB_REQUIRED=2.25.11
 GTK_REQUIRED=2.99.3
 GTHREAD_REQUIRED=2.0.0
 GTK_VNC_REQUIRED=0.4.3
+SPICE_GTK_REQUIRED=0.5
 AVAHI_REQUIRED=0.6.26
 PANEL_REQUIRED_VERSION=2.0.0
 TELEPATHY_GLIB_REQUIRED=0.11.6
@@ -215,6 +216,21 @@ if test "x$enable_applet" = "xyes";then
 fi
 AM_CONDITIONAL(APPLET, test "x$enable_applet" = "xyes")
 
+AC_ARG_ENABLE(spice,
+              AS_HELP_STRING([--enable-spice],
+                             [Enable SPICE plugin (default=yes)]),
+              enable_spice=${enableval},
+              enable_spice=yes)
+
+if test "x$enable_spice" = "xyes" ; then
+  PKG_CHECK_MODULES(SPICE,\
+  spice-client-gtk-3.0 >= $SPICE_GTK_REQUIRED)
+  AC_SUBST(SPICE_CFLAGS)
+  AC_SUBST(SPICE_LIBS)
+  AC_DEFINE([VINAGRE_ENABLE_SPICE],[1],[We want spice support])
+fi
+AM_CONDITIONAL(SPICE, test "x$enable_spice" = "xyes")
+
 GLIB_GSETTINGS
 
 dnl Check for bind in libsocket (needed on Solaris)
@@ -303,6 +319,7 @@ plugins/Makefile
 plugins/vnc/Makefile
 plugins/ssh/Makefile
 plugins/rdp/Makefile
+plugins/spice/Makefile
 plugins/reverse-vnc/Makefile
 plugins/im-status/Makefile
 vinagre/Makefile
@@ -327,4 +344,5 @@ Configure summary:
 	Telepathy support ..........:  ${have_telepathy}
 	SSH plugin .................:  ${enable_ssh}
 	RDP plugin .................:  ${enable_rdp}
+	Spice plugin ...............:  ${enable_spice}
 "
diff --git a/data/.gitignore b/data/.gitignore
index a5fd0a8..3ee935c 100644
--- a/data/.gitignore
+++ b/data/.gitignore
@@ -28,5 +28,3 @@
 /vinagre.desktop
 /vinagre.desktop.in
 /vinagre.pc
-/vinagre.schemas.in
-/*.gschema.valid
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index ce85df8..bcf40af 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -1,21 +1,30 @@
-DIST_SUBDIRS =	\
-  vnc		\
-  ssh		\
-  rdp		\
-  reverse-vnc	\
-  im-status
+NULL =
 
-SUBDIRS =	\
-  vnc		\
-  reverse-vnc	\
-  im-status
+DIST_SUBDIRS =					\
+	im-status				\
+	rdp					\
+	reverse-vnc				\
+	spice					\
+	ssh					\
+	vnc					\
+	$(NULL)
 
-if SSH
-SUBDIRS += ssh
-endif
+SUBDIRS =					\
+	im-status				\
+	reverse-vnc				\
+	vnc					\
+	$(NULL)
 
 if RDP
 SUBDIRS += rdp
 endif
 
+if SPICE
+SUBDIRS += spice
+endif
+
+if SSH
+SUBDIRS += ssh
+endif
+
 -include $(top_srcdir)/git.mk
diff --git a/plugins/spice/Makefile.am b/plugins/spice/Makefile.am
new file mode 100644
index 0000000..36b3afa
--- /dev/null
+++ b/plugins/spice/Makefile.am
@@ -0,0 +1,33 @@
+# spice plugin
+plugindir = $(VINAGRE_PLUGINS_LIBS_DIR)
+
+NULL =
+INCLUDES =					\
+	-I$(top_srcdir)				\
+	$(DISABLE_DEPRECATED_CFLAGS)		\
+	$(SPICE_CFLAGS)				\
+	$(VINAGRE_CFLAGS)			\
+	$(WARN_CFLAGS)				\
+	$(NULL)
+
+plugin_LTLIBRARIES = libspice.la
+
+libspice_la_SOURCES =						\
+	vinagre-spice-plugin.h vinagre-spice-plugin.c		\
+	vinagre-spice-connection.h vinagre-spice-connection.c	\
+	vinagre-spice-tab.h vinagre-spice-tab.c	\
+	vinagre-spice-tunnel.h vinagre-spice-tunnel.c	\
+	$(NULL)
+
+libspice_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS)
+libspice_la_LIBADD  = $(SPICE_LIBS)
+
+plugin_in_files = spice.plugin.desktop.in
+%.plugin: %.plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(AM_V_GEN)$(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
+plugin_DATA = $(plugin_in_files:.plugin.desktop.in=.plugin)
+
+EXTRA_DIST = $(plugin_in_files)
+CLEANFILES = $(plugin_DATA)
+DISTCLEANFILES = $(plugin_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/spice/spice.plugin.desktop.in b/plugins/spice/spice.plugin.desktop.in
new file mode 100644
index 0000000..2416f41
--- /dev/null
+++ b/plugins/spice/spice.plugin.desktop.in
@@ -0,0 +1,9 @@
+[Plugin]
+Module=spice
+IAge=1
+_Name=Spice
+_Description=Spice support
+Authors=Marc-André Lureau
+Copyright=Copyright © 2011 Red Hat, Inc.
+Website=http://www.spice-space.org
+Version=1.0
diff --git a/plugins/spice/vinagre-spice-connection.c b/plugins/spice/vinagre-spice-connection.c
new file mode 100644
index 0000000..210b462
--- /dev/null
+++ b/plugins/spice/vinagre-spice-connection.c
@@ -0,0 +1,551 @@
+/*
+ * vinagre-spice-connection.c
+ * Child class of abstract VinagreConnection, specific to SPICE protocol
+ * This file is part of vinagre
+ *
+ * Copyright (C) 2011 Red Hat, Inc.
+ * Author: Marc-Andre Lureau <marcandre lureau redhat com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n.h>
+#include <stdlib.h>
+#include <vinagre/vinagre-utils.h>
+#include <vinagre/vinagre-cache-prefs.h>
+
+#include "vinagre-spice-connection.h"
+
+struct _VinagreSpiceConnectionPrivate
+{
+  gchar	   *desktop_name;
+  gboolean view_only;
+  gboolean scaling;
+  gboolean resize_guest;
+  gboolean auto_clipboard;
+  gint	   fd;
+  gchar	   *ssh_tunnel_host;
+  GSocket  *socket;
+};
+
+enum
+{
+  PROP_0,
+  PROP_DESKTOP_NAME,
+  PROP_VIEW_ONLY,
+  PROP_SCALING,
+  PROP_RESIZE_GUEST,
+  PROP_AUTO_CLIPBOARD,
+  PROP_FD,
+  PROP_SSH_TUNNEL_HOST,
+  PROP_SOCKET
+};
+
+#define VINAGRE_SPICE_CONNECTION_PRIVATE(o)  (G_TYPE_INSTANCE_GET_PRIVATE ((o), VINAGRE_TYPE_SPICE_CONNECTION, VinagreSpiceConnectionPrivate))
+G_DEFINE_TYPE (VinagreSpiceConnection, vinagre_spice_connection, VINAGRE_TYPE_CONNECTION);
+
+static void
+vinagre_spice_connection_init (VinagreSpiceConnection *conn)
+{
+  conn->priv = G_TYPE_INSTANCE_GET_PRIVATE (conn, VINAGRE_TYPE_SPICE_CONNECTION, VinagreSpiceConnectionPrivate);
+
+  conn->priv->desktop_name = NULL;
+  conn->priv->view_only = FALSE;
+  conn->priv->scaling = FALSE;
+  conn->priv->resize_guest = TRUE;
+  conn->priv->auto_clipboard = TRUE;
+  conn->priv->fd = 0;
+  conn->priv->ssh_tunnel_host = NULL;
+  conn->priv->socket = NULL;
+}
+
+static void
+vinagre_spice_connection_constructed (GObject *object)
+{
+  vinagre_connection_set_protocol (VINAGRE_CONNECTION (object), "spice");
+}
+
+static void
+vinagre_spice_connection_finalize (GObject *object)
+{
+  VinagreSpiceConnection *conn = VINAGRE_SPICE_CONNECTION (object);
+
+  g_free (conn->priv->desktop_name);
+  g_free (conn->priv->ssh_tunnel_host);
+
+  G_OBJECT_CLASS (vinagre_spice_connection_parent_class)->finalize (object);
+}
+
+static void
+vinagre_spice_connection_dispose (GObject *object)
+{
+  VinagreSpiceConnection *conn = VINAGRE_SPICE_CONNECTION (object);
+
+  if (conn->priv->socket)
+    {
+      g_object_unref (conn->priv->socket);
+      conn->priv->socket = NULL;
+    }
+
+  G_OBJECT_CLASS (vinagre_spice_connection_parent_class)->dispose (object);
+}
+
+static void
+vinagre_spice_connection_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+  VinagreSpiceConnection *conn;
+
+  g_return_if_fail (VINAGRE_IS_SPICE_CONNECTION (object));
+
+  conn = VINAGRE_SPICE_CONNECTION (object);
+
+  switch (prop_id)
+    {
+    case PROP_DESKTOP_NAME:
+      vinagre_spice_connection_set_desktop_name (conn, g_value_get_string (value));
+      break;
+
+    case PROP_VIEW_ONLY:
+      vinagre_spice_connection_set_view_only (conn, g_value_get_boolean (value));
+      break;
+
+    case PROP_SCALING:
+      vinagre_spice_connection_set_scaling (conn, g_value_get_boolean (value));
+      break;
+
+    case PROP_RESIZE_GUEST:
+      vinagre_spice_connection_set_resize_guest (conn, g_value_get_boolean (value));
+      break;
+
+    case PROP_AUTO_CLIPBOARD:
+      vinagre_spice_connection_set_auto_clipboard (conn, g_value_get_boolean (value));
+      break;
+
+    case PROP_SSH_TUNNEL_HOST:
+      vinagre_spice_connection_set_ssh_tunnel_host (conn, g_value_get_string (value));
+      break;
+
+    case PROP_FD:
+      vinagre_spice_connection_set_fd (conn, g_value_get_int (value));
+      break;
+
+    case PROP_SOCKET:
+      vinagre_spice_connection_set_socket (conn, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+vinagre_spice_connection_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+  VinagreSpiceConnection *conn;
+
+  g_return_if_fail (VINAGRE_IS_SPICE_CONNECTION (object));
+
+  conn = VINAGRE_SPICE_CONNECTION (object);
+
+  switch (prop_id)
+    {
+    case PROP_DESKTOP_NAME:
+      g_value_set_string (value, conn->priv->desktop_name);
+      break;
+
+    case PROP_VIEW_ONLY:
+      g_value_set_boolean (value, conn->priv->view_only);
+      break;
+
+    case PROP_SCALING:
+      g_value_set_boolean (value, conn->priv->scaling);
+      break;
+
+    case PROP_RESIZE_GUEST:
+      g_value_set_boolean (value, conn->priv->resize_guest);
+      break;
+
+    case PROP_AUTO_CLIPBOARD:
+      g_value_set_boolean (value, conn->priv->auto_clipboard);
+      break;
+
+    case PROP_SSH_TUNNEL_HOST:
+      g_value_set_string (value, conn->priv->ssh_tunnel_host);
+      break;
+
+    case PROP_FD:
+      g_value_set_int (value, vinagre_spice_connection_get_fd (conn));
+      break;
+
+    case PROP_SOCKET:
+      g_value_set_object (value, conn->priv->socket);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+spice_fill_writer (VinagreConnection *conn, xmlTextWriter *writer)
+{
+  VinagreSpiceConnection *spice_conn = VINAGRE_SPICE_CONNECTION (conn);
+  VINAGRE_CONNECTION_CLASS (vinagre_spice_connection_parent_class)->impl_fill_writer (conn, writer);
+
+  xmlTextWriterWriteFormatElement (writer, (const xmlChar *)"view_only", "%d", spice_conn->priv->view_only);
+  xmlTextWriterWriteFormatElement (writer, (const xmlChar *)"scaling", "%d", spice_conn->priv->scaling);
+  xmlTextWriterWriteFormatElement (writer, (const xmlChar *)"resize_guest", "%d", spice_conn->priv->resize_guest);
+  xmlTextWriterWriteFormatElement (writer, (const xmlChar *)"auto_clipboard", "%d", spice_conn->priv->auto_clipboard);
+
+  if (spice_conn->priv->ssh_tunnel_host && *spice_conn->priv->ssh_tunnel_host)
+    xmlTextWriterWriteFormatElement (writer, (const xmlChar *)"ssh_tunnel_host", "%s", spice_conn->priv->ssh_tunnel_host);
+}
+
+static void
+spice_parse_item (VinagreConnection *conn, xmlNode *root)
+{
+  xmlNode *curr;
+  xmlChar *s_value;
+  VinagreSpiceConnection *spice_conn = VINAGRE_SPICE_CONNECTION (conn);
+
+  VINAGRE_CONNECTION_CLASS (vinagre_spice_connection_parent_class)->impl_parse_item (conn, root);
+
+  for (curr = root->children; curr; curr = curr->next)
+    {
+      s_value = xmlNodeGetContent (curr);
+
+      if (!xmlStrcmp(curr->name, (const xmlChar *)"view_only"))
+	{
+	  vinagre_spice_connection_set_view_only (spice_conn, vinagre_utils_parse_boolean ((const gchar *)s_value));
+	}
+      else if (!xmlStrcmp(curr->name, (const xmlChar *)"scaling"))
+	{
+	  vinagre_spice_connection_set_scaling (spice_conn, vinagre_utils_parse_boolean ((const gchar *)s_value));
+	}
+      else if (!xmlStrcmp(curr->name, (const xmlChar *)"resize_guest"))
+	{
+	  vinagre_spice_connection_set_resize_guest (spice_conn, vinagre_utils_parse_boolean ((const gchar *)s_value));
+	}
+      else if (!xmlStrcmp(curr->name, (const xmlChar *)"auto_clipboard"))
+	{
+	  vinagre_spice_connection_set_auto_clipboard (spice_conn, vinagre_utils_parse_boolean ((const gchar *)s_value));
+	}
+      else if (!xmlStrcmp(curr->name, (const xmlChar *)"ssh_tunnel_host"))
+	{
+	  vinagre_spice_connection_set_ssh_tunnel_host (spice_conn, (const gchar *)s_value);
+	}
+
+      xmlFree (s_value);
+    }
+}
+
+static gchar *
+spice_get_best_name (VinagreConnection *conn)
+{
+  VinagreSpiceConnection *spice_conn = VINAGRE_SPICE_CONNECTION (conn);
+
+  if (vinagre_connection_get_name (conn))
+    return g_strdup (vinagre_connection_get_name (conn));
+
+  if (spice_conn->priv->desktop_name)
+    return g_strdup (spice_conn->priv->desktop_name);
+
+  if (vinagre_connection_get_host (conn))
+    return vinagre_connection_get_string_rep (conn, FALSE);
+
+  return NULL;
+}
+
+static void
+spice_parse_options_widget (VinagreConnection *conn, GtkWidget *widget)
+{
+  GtkWidget *view_only, *scaling, *ssh_host, *resize_guest, *auto_clipboard;
+
+  view_only = g_object_get_data (G_OBJECT (widget), "view_only");
+  scaling = g_object_get_data (G_OBJECT (widget), "scaling");
+  resize_guest = g_object_get_data (G_OBJECT (widget), "resize_guest");
+  auto_clipboard = g_object_get_data (G_OBJECT (widget), "auto_clipboard");
+  ssh_host = g_object_get_data (G_OBJECT (widget), "ssh_host");
+  if (!view_only || !scaling || !ssh_host || !resize_guest || !auto_clipboard)
+    {
+      g_warning ("Wrong widget passed to spice_parse_options_widget()");
+      return;
+    }
+
+  vinagre_cache_prefs_set_boolean ("spice-connection", "view-only", gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (view_only)));
+  vinagre_cache_prefs_set_boolean ("spice-connection", "scaling", gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (scaling)));
+  vinagre_cache_prefs_set_boolean ("spice-connection", "resize-guest", gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (resize_guest)));
+  vinagre_cache_prefs_set_boolean ("spice-connection", "auto-clipboard", gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (auto_clipboard)));
+  vinagre_cache_prefs_set_string  ("spice-connection", "ssh-tunnel-host", gtk_entry_get_text (GTK_ENTRY (ssh_host)));
+
+  g_object_set (conn,
+		"resize-guest", gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (resize_guest)),
+		"auto-clipboard", gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (auto_clipboard)),
+		"view-only", gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (view_only)),
+		"scaling", gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (scaling)),
+		"ssh-tunnel-host", gtk_entry_get_text (GTK_ENTRY (ssh_host)),
+		NULL);
+}
+
+static void
+vinagre_spice_connection_class_init (VinagreSpiceConnectionClass *klass)
+{
+  GObjectClass* object_class = G_OBJECT_CLASS (klass);
+  VinagreConnectionClass* parent_class = VINAGRE_CONNECTION_CLASS (klass);
+
+  g_type_class_add_private (klass, sizeof (VinagreSpiceConnectionPrivate));
+
+  object_class->finalize = vinagre_spice_connection_finalize;
+  object_class->dispose = vinagre_spice_connection_dispose;
+  object_class->set_property = vinagre_spice_connection_set_property;
+  object_class->get_property = vinagre_spice_connection_get_property;
+  object_class->constructed  = vinagre_spice_connection_constructed;
+
+  parent_class->impl_fill_writer = spice_fill_writer;
+  parent_class->impl_parse_item	 = spice_parse_item;
+  parent_class->impl_get_best_name = spice_get_best_name;
+  parent_class->impl_parse_options_widget = spice_parse_options_widget;
+
+  g_object_class_install_property (object_class,
+				   PROP_DESKTOP_NAME,
+				   g_param_spec_string ("desktop-name",
+							"desktop-name",
+							"name of this connection as reported by the server",
+							NULL,
+							G_PARAM_READWRITE |
+							G_PARAM_CONSTRUCT |
+							G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class,
+				   PROP_VIEW_ONLY,
+				   g_param_spec_boolean ("view-only",
+							 "View-only connection",
+							 "Whether this connection is a view-only one",
+							 FALSE,
+							 G_PARAM_READWRITE |
+							 G_PARAM_CONSTRUCT |
+							 G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class,
+				   PROP_SCALING,
+				   g_param_spec_boolean ("scaling",
+							 "Use scaling",
+							 "Whether to use scaling on this connection",
+							 FALSE,
+							 G_PARAM_READWRITE |
+							 G_PARAM_CONSTRUCT |
+							 G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class,
+				   PROP_RESIZE_GUEST,
+				   g_param_spec_boolean ("resize-guest",
+							 "Resize guest",
+							 "Whether to use guest resize on this connection",
+							 TRUE,
+							 G_PARAM_READWRITE |
+							 G_PARAM_CONSTRUCT |
+							 G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class,
+				   PROP_RESIZE_GUEST,
+				   g_param_spec_boolean ("auto-clipboard",
+							 "Auto clipboard",
+							 "Whether clipboard sharing is automatic",
+							 TRUE,
+							 G_PARAM_READWRITE |
+							 G_PARAM_CONSTRUCT |
+							 G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class,
+				   PROP_FD,
+				   g_param_spec_int ("fd",
+						     "file descriptor",
+						     "the file descriptor for this connection",
+						     0,
+						     G_MAXINT,
+						     0,
+						     G_PARAM_READWRITE |
+						     G_PARAM_CONSTRUCT |
+						     G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class,
+				   PROP_SSH_TUNNEL_HOST,
+				   g_param_spec_string ("ssh-tunnel-host",
+							"SSH Tunnel Host",
+							"hostname used to create the SSH tunnel",
+							NULL,
+							G_PARAM_READWRITE |
+							G_PARAM_CONSTRUCT |
+							G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (object_class,
+				   PROP_SOCKET,
+				   g_param_spec_object ("socket",
+							"Socket",
+							"A GSocket for this connection",
+							G_TYPE_SOCKET,
+							G_PARAM_READWRITE |
+							G_PARAM_CONSTRUCT |
+							G_PARAM_STATIC_STRINGS));
+
+}
+
+VinagreConnection *
+vinagre_spice_connection_new (void)
+{
+  return VINAGRE_CONNECTION (g_object_new (VINAGRE_TYPE_SPICE_CONNECTION, NULL));
+}
+
+void
+vinagre_spice_connection_set_desktop_name (VinagreSpiceConnection *conn,
+					   const gchar *desktop_name)
+{
+  g_return_if_fail (VINAGRE_IS_SPICE_CONNECTION (conn));
+
+  g_free (conn->priv->desktop_name);
+  conn->priv->desktop_name = g_strdup (desktop_name);
+}
+
+const gchar *
+vinagre_spice_connection_get_desktop_name (VinagreSpiceConnection *conn)
+{
+  g_return_val_if_fail (VINAGRE_IS_SPICE_CONNECTION (conn), NULL);
+
+  return conn->priv->desktop_name;
+}
+
+void
+vinagre_spice_connection_set_view_only (VinagreSpiceConnection *conn,
+					gboolean value)
+{
+  g_return_if_fail (VINAGRE_IS_SPICE_CONNECTION (conn));
+
+  conn->priv->view_only = value;
+}
+gboolean
+vinagre_spice_connection_get_view_only (VinagreSpiceConnection *conn)
+{
+  g_return_val_if_fail (VINAGRE_IS_SPICE_CONNECTION (conn), FALSE);
+
+  return conn->priv->view_only;
+}
+
+void
+vinagre_spice_connection_set_scaling (VinagreSpiceConnection *conn,
+				      gboolean value)
+{
+  g_return_if_fail (VINAGRE_IS_SPICE_CONNECTION (conn));
+
+  conn->priv->scaling = value;
+}
+gboolean
+vinagre_spice_connection_get_scaling (VinagreSpiceConnection *conn)
+{
+  g_return_val_if_fail (VINAGRE_IS_SPICE_CONNECTION (conn), FALSE);
+
+  return conn->priv->scaling;
+}
+
+void
+vinagre_spice_connection_set_resize_guest (VinagreSpiceConnection *conn,
+					   gboolean value)
+{
+  g_return_if_fail (VINAGRE_IS_SPICE_CONNECTION (conn));
+
+  conn->priv->resize_guest = value;
+}
+gboolean
+vinagre_spice_connection_get_resize_guest (VinagreSpiceConnection *conn)
+{
+  g_return_val_if_fail (VINAGRE_IS_SPICE_CONNECTION (conn), FALSE);
+
+  return conn->priv->resize_guest;
+}
+
+void
+vinagre_spice_connection_set_auto_clipboard (VinagreSpiceConnection *conn,
+					     gboolean value)
+{
+  g_return_if_fail (VINAGRE_IS_SPICE_CONNECTION (conn));
+
+  conn->priv->auto_clipboard = value;
+}
+
+gboolean
+vinagre_spice_connection_get_auto_clipboard (VinagreSpiceConnection *conn)
+{
+  g_return_val_if_fail (VINAGRE_IS_SPICE_CONNECTION (conn), FALSE);
+
+  return conn->priv->auto_clipboard;
+}
+
+void
+vinagre_spice_connection_set_fd (VinagreSpiceConnection *conn,
+				 gint		         value)
+{
+  g_return_if_fail (VINAGRE_IS_SPICE_CONNECTION (conn));
+  g_return_if_fail (value >= 0);
+
+  conn->priv->fd = value;
+}
+
+gint
+vinagre_spice_connection_get_fd (VinagreSpiceConnection *conn)
+{
+  g_return_val_if_fail (VINAGRE_IS_SPICE_CONNECTION (conn), 0);
+
+  if (conn->priv->socket)
+    return g_socket_get_fd (conn->priv->socket);
+  else
+    return conn->priv->fd;
+}
+
+void
+vinagre_spice_connection_set_socket (VinagreSpiceConnection *conn,
+				     GSocket		    *socket)
+{
+  g_return_if_fail (VINAGRE_IS_SPICE_CONNECTION (conn));
+
+  if (socket)
+    conn->priv->socket = g_object_ref (socket);
+}
+
+GSocket *
+vinagre_spice_connection_get_socket (VinagreSpiceConnection *conn)
+{
+  g_return_val_if_fail (VINAGRE_IS_SPICE_CONNECTION (conn), 0);
+
+  return conn->priv->socket;
+}
+
+void
+vinagre_spice_connection_set_ssh_tunnel_host (VinagreSpiceConnection *conn,
+					      const gchar *host)
+{
+  g_return_if_fail (VINAGRE_IS_SPICE_CONNECTION (conn));
+
+  g_free (conn->priv->ssh_tunnel_host);
+  conn->priv->ssh_tunnel_host = g_strdup (host);
+}
+
+const gchar *
+vinagre_spice_connection_get_ssh_tunnel_host (VinagreSpiceConnection *conn)
+{
+  g_return_val_if_fail (VINAGRE_IS_SPICE_CONNECTION (conn), NULL);
+
+  return conn->priv->ssh_tunnel_host;
+}
+
+/* vim: set ts=8: */
diff --git a/plugins/spice/vinagre-spice-connection.h b/plugins/spice/vinagre-spice-connection.h
new file mode 100644
index 0000000..31165a7
--- /dev/null
+++ b/plugins/spice/vinagre-spice-connection.h
@@ -0,0 +1,92 @@
+/*
+ * vinagre-spice-connection.h
+ * Child class of abstract VinagreConnection, specific to SPICE protocol
+ * This file is part of vinagre
+ *
+ * Copyright (C) 2011 Red Hat, Inc.
+ * Author: Marc-Andre Lureau <marcandre lureau redhat com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __VINAGRE_SPICE_CONNECTION_H__
+#define __VINAGRE_SPICE_CONNECTION_H__
+
+#include <vinagre/vinagre-connection.h>
+
+G_BEGIN_DECLS
+
+#define VINAGRE_TYPE_SPICE_CONNECTION             (vinagre_spice_connection_get_type ())
+#define VINAGRE_SPICE_CONNECTION(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), VINAGRE_TYPE_SPICE_CONNECTION, VinagreSpiceConnection))
+#define VINAGRE_SPICE_CONNECTION_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), VINAGRE_TYPE_SPICE_CONNECTION, VinagreSpiceConnectionClass))
+#define VINAGRE_IS_SPICE_CONNECTION(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), VINAGRE_TYPE_SPICE_CONNECTION))
+#define VINAGRE_IS_SPICE_CONNECTION_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), VINAGRE_TYPE_SPICE_CONNECTION))
+#define VINAGRE_SPICE_CONNECTION_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), VINAGRE_TYPE_SPICE_CONNECTION, VinagreSpiceConnectionClass))
+
+typedef struct _VinagreSpiceConnectionClass   VinagreSpiceConnectionClass;
+typedef struct _VinagreSpiceConnection        VinagreSpiceConnection;
+typedef struct _VinagreSpiceConnectionPrivate VinagreSpiceConnectionPrivate;
+
+struct _VinagreSpiceConnectionClass
+{
+  VinagreConnectionClass parent_class;
+};
+
+struct _VinagreSpiceConnection
+{
+  VinagreConnection parent_instance;
+  VinagreSpiceConnectionPrivate *priv;
+};
+
+
+GType vinagre_spice_connection_get_type (void) G_GNUC_CONST;
+
+VinagreConnection*  vinagre_spice_connection_new (void);
+
+const gchar*	    vinagre_spice_connection_get_desktop_name (VinagreSpiceConnection *conn);
+void		    vinagre_spice_connection_set_desktop_name (VinagreSpiceConnection *conn,
+							       const gchar *desktop_name);
+
+gboolean	    vinagre_spice_connection_get_view_only    (VinagreSpiceConnection *conn);
+void		    vinagre_spice_connection_set_view_only    (VinagreSpiceConnection *conn,
+							       gboolean value);
+
+gboolean	    vinagre_spice_connection_get_scaling      (VinagreSpiceConnection *conn);
+void		    vinagre_spice_connection_set_scaling      (VinagreSpiceConnection *conn,
+							       gboolean value);
+
+gboolean	    vinagre_spice_connection_get_resize_guest (VinagreSpiceConnection *conn);
+void		    vinagre_spice_connection_set_resize_guest (VinagreSpiceConnection *conn,
+							       gboolean value);
+
+gint		    vinagre_spice_connection_get_fd	      (VinagreSpiceConnection *conn);
+void		    vinagre_spice_connection_set_fd	      (VinagreSpiceConnection *conn,
+							       gint value);
+
+GSocket *	    vinagre_spice_connection_get_socket	      (VinagreSpiceConnection *conn);
+void		    vinagre_spice_connection_set_socket	      (VinagreSpiceConnection *conn,
+							       GSocket *socket);
+
+const gchar*	    vinagre_spice_connection_get_ssh_tunnel_host (VinagreSpiceConnection *conn);
+void		    vinagre_spice_connection_set_ssh_tunnel_host (VinagreSpiceConnection *conn,
+								  const gchar *host);
+
+gboolean	    vinagre_spice_connection_get_auto_clipboard (VinagreSpiceConnection *conn);
+void		    vinagre_spice_connection_set_auto_clipboard (VinagreSpiceConnection *conn,
+								 gboolean value);
+
+G_END_DECLS
+
+#endif /* __VINAGRE_SPICE_CONNECTION_H__  */
+/* vim: set ts=8: */
diff --git a/plugins/spice/vinagre-spice-plugin.c b/plugins/spice/vinagre-spice-plugin.c
new file mode 100644
index 0000000..2a1c182
--- /dev/null
+++ b/plugins/spice/vinagre-spice-plugin.c
@@ -0,0 +1,360 @@
+/*
+ * vinagre-spice-plugin.c
+ * This file is part of vinagre
+ *
+ * Copyright (C) 2011 Red Hat, Inc.
+ * Author: Marc-Andre Lureau <marcandre lureau redhat com>
+ *
+ * vinagre-spice-plugin.c is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * vinagre-spice-plugin.c 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n-lib.h>
+#include <gmodule.h>
+
+#include <vinagre/vinagre-cache-prefs.h>
+#include <vinagre/vinagre-protocol.h>
+
+#include "vinagre-spice-plugin.h"
+#include "vinagre-spice-connection.h"
+#include "vinagre-spice-tab.h"
+
+static void vinagre_spice_protocol_iface_init (VinagreProtocolInterface *iface);
+
+G_DEFINE_DYNAMIC_TYPE_EXTENDED (VinagreSpicePlugin,
+				vinagre_spice_plugin,
+				PEAS_TYPE_EXTENSION_BASE,
+				0,
+				G_IMPLEMENT_INTERFACE_DYNAMIC (VINAGRE_TYPE_PROTOCOL,
+							       vinagre_spice_protocol_iface_init))
+
+static const gchar *
+impl_get_protocol (VinagreProtocol *plugin)
+{
+  return "spice";
+}
+
+static const gchar *
+impl_get_mdns_service (VinagreProtocol *plugin)
+{
+  return "_spice._tcp";
+}
+
+static VinagreConnection *
+impl_new_connection (VinagreProtocol *plugin)
+{
+  VinagreConnection *conn;
+
+  conn = vinagre_spice_connection_new ();
+
+  return conn;
+}
+
+static VinagreConnection *
+impl_new_connection_from_file (VinagreProtocol *plugin,
+			       const gchar   *data,
+			       gboolean	      use_bookmarks,
+			       gchar	    **error_msg)
+{
+  GKeyFile	    *file;
+  GError	    *error;
+  gboolean	     loaded;
+  gchar		    *host, *actual_host, *protocol;
+  gint		     port;
+  VinagreConnection *conn;
+
+  *error_msg = NULL;
+  conn = NULL;
+  host = NULL;
+  protocol = NULL;
+  error = NULL;
+
+  file = g_key_file_new ();
+  loaded = g_key_file_load_from_data (file,
+				      data,
+				      -1,
+				      0,
+				      &error);
+  if (!loaded)
+    {
+      if (error)
+	{
+	  *error_msg = g_strdup (error->message);
+	  g_error_free (error);
+	}
+      else
+	*error_msg = g_strdup (_("Could not parse the file."));
+
+      goto the_end;
+    }
+  if (!g_key_file_has_group (file, "connection"))
+    {
+      /* Translators: Do not translate "connection". It's the name of a group in the .spice (.ini like) file. */
+      *error_msg = g_strdup (_("The file is not a Spice one: Missing the group \"connection\"."));
+      goto the_end;
+    }
+
+  if (!g_key_file_has_key (file, "connection", "host", NULL))
+    {
+      /* Translators: Do not translate "host". It's the name of a key in the .spice (.ini like) file. */
+      *error_msg = g_strdup (_("The file is not a Spice one: Missing the key \"host\"."));
+      goto the_end;
+    }
+
+  host = g_key_file_get_string (file, "connection", "host", NULL);
+  port = g_key_file_get_integer (file, "connection", "port", NULL);
+  if (!port)
+    {
+      if (!vinagre_connection_split_string (host, "spice", &protocol, &actual_host, &port, error_msg))
+	goto the_end;
+
+      g_free (host);
+      host = actual_host;
+    }
+
+  if (use_bookmarks)
+    conn = vinagre_bookmarks_exists (vinagre_bookmarks_get_default (), "spice", host, port);
+  if (!conn)
+    {
+      gchar *s_value;
+
+      conn = vinagre_spice_connection_new ();
+      vinagre_connection_set_host (conn, host);
+      vinagre_connection_set_port (conn, port);
+
+      s_value = g_key_file_get_string  (file, "connection", "password", NULL);
+      vinagre_connection_set_password (conn, s_value);
+      g_free (s_value);
+    }
+
+ the_end:
+
+  g_free (host);
+  g_free (protocol);
+  g_key_file_free (file);
+  return conn;
+
+}
+
+static gchar **
+impl_get_public_description (VinagreProtocol *plugin)
+{
+  gchar **result = g_new (gchar *, 3);
+
+  result[0] = g_strdup (_("SPICE"));
+  /* Translators: This is a description of the SPICE protocol. It appears at Connect dialog. */
+  result[1] = g_strdup (_("Access Spice desktop server"));
+  result[2] = NULL;
+
+  return result;
+}
+
+static gint
+impl_get_default_port (VinagreProtocol *plugin)
+{
+  return 5900;
+}
+
+static void
+vinagre_spice_plugin_init (VinagreSpicePlugin *plugin)
+{
+}
+
+static GtkWidget *
+impl_new_tab (VinagreProtocol *plugin,
+	      VinagreConnection *conn,
+	      VinagreWindow	*window)
+{
+  return vinagre_spice_tab_new (conn, window);
+}
+
+static void
+ssh_tunnel_check_toggled_cb (GtkToggleButton *button, GObject *box)
+{
+  gboolean active = gtk_toggle_button_get_active (button);
+  GtkWidget *ssh_host_entry = g_object_get_data (G_OBJECT (box), "ssh_host");
+
+  gtk_widget_set_sensitive (ssh_host_entry, active);
+
+  if (active)
+    gtk_widget_grab_focus (ssh_host_entry);
+  else
+    gtk_entry_set_text (GTK_ENTRY (ssh_host_entry), "");
+}
+
+static GtkWidget *
+impl_get_connect_widget (VinagreProtocol *plugin, VinagreConnection *conn)
+{
+  GtkWidget *box, *label, *check, *p_entry, *box2, *ssh_host_entry;
+  GtkTable  *table;
+  gchar	    *str, *ssh_host;
+  gboolean  has_conn = VINAGRE_IS_SPICE_CONNECTION (conn);
+
+  box = gtk_vbox_new (FALSE, 0);
+
+  str = g_strdup_printf ("<b>%s</b>", _("SPICE Options"));
+  label = gtk_label_new (str);
+  g_free (str);
+  gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
+  gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+  gtk_misc_set_padding (GTK_MISC (label), 0, 6);
+  gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
+
+  table = GTK_TABLE (gtk_table_new (6, 2, FALSE));
+  label = gtk_label_new ("  ");
+  gtk_table_attach (table, label, 0, 1, 0, 1, GTK_SHRINK, GTK_SHRINK, 0, 0);
+
+  /* View only check button - not fully ready on spice-gtk side */
+  check = gtk_check_button_new_with_mnemonic (_("_View only"));
+  g_object_set_data (G_OBJECT (box), "view_only", check);
+  /* gtk_table_attach_defaults (table, check, 1, 2, 0, 1); */
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check),
+				has_conn ? vinagre_spice_connection_get_view_only (VINAGRE_SPICE_CONNECTION (conn))
+				: vinagre_cache_prefs_get_boolean ("spice-connection", "view-only", FALSE));
+
+  /* Resize guest check button */
+  check = gtk_check_button_new_with_mnemonic (_("_Resize guest"));
+  g_object_set_data (G_OBJECT (box), "resize_guest", check);
+  gtk_table_attach_defaults (table, check, 1, 2, 0, 1);
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check),
+				has_conn ? vinagre_spice_connection_get_resize_guest (VINAGRE_SPICE_CONNECTION (conn))
+				: vinagre_cache_prefs_get_boolean ("spice-connection", "resize-guest", TRUE));
+
+  /* Clipboard sharing check button */
+  check = gtk_check_button_new_with_mnemonic (_("_Share clipboard"));
+  g_object_set_data (G_OBJECT (box), "auto_clipboard", check);
+  gtk_table_attach_defaults (table, check, 1, 2, 1, 2);
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check),
+				has_conn ? vinagre_spice_connection_get_auto_clipboard (VINAGRE_SPICE_CONNECTION (conn))
+				: vinagre_cache_prefs_get_boolean ("spice-connection", "auto-clipboard", TRUE));
+
+  /* Scaling check button */
+  check = gtk_check_button_new_with_mnemonic (_("_Scaling"));
+  g_object_set_data (G_OBJECT (box), "scaling", check);
+  gtk_table_attach_defaults (table, check, 1, 2, 2, 3);
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check),
+				has_conn ? vinagre_spice_connection_get_scaling (VINAGRE_SPICE_CONNECTION (conn))
+				: vinagre_cache_prefs_get_boolean ("spice-connection", "scaling", FALSE));
+
+  /* SSH Tunneling */
+  box2 = gtk_hbox_new (FALSE, 5);
+
+  /* Translators: the whole sentence will be: Use host <hostname> as a SSH tunnel*/
+  check = gtk_check_button_new_with_mnemonic (_("Use h_ost"));
+  g_object_set_data (G_OBJECT (box), "use_ssh", check);
+  gtk_box_pack_start (GTK_BOX (box2), check, FALSE, FALSE, 0);
+
+  ssh_host_entry = gtk_entry_new ();
+  gtk_widget_set_sensitive (ssh_host_entry, FALSE);
+  g_object_set_data (G_OBJECT (box), "ssh_host", ssh_host_entry);
+  /* Translators: This is the tooltip of the SSH tunneling entry */
+  str = g_strdup_printf ("%s\n%s\n%s",
+			 _("hostname or user hostname"),
+			 _("Supply an alternative port using colon"),
+			 _("For instance: joe domain com:5022"));
+  gtk_widget_set_tooltip_text (ssh_host_entry, str);
+  g_free (str);
+  gtk_box_pack_start (GTK_BOX (box2), ssh_host_entry, FALSE, FALSE, 0);
+
+  /* Translators: the whole sentence will be: Use host <hostname> as a SSH tunnel*/
+  label = gtk_label_new_with_mnemonic (_("as a SSH tunnel"));
+  gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+  gtk_box_pack_start (GTK_BOX (box2), label, FALSE, FALSE, 0);
+
+  g_signal_connect (check,
+		    "toggled",
+		    G_CALLBACK (ssh_tunnel_check_toggled_cb),
+		    box);
+
+  ssh_host = has_conn ? g_strdup (vinagre_spice_connection_get_ssh_tunnel_host (VINAGRE_SPICE_CONNECTION (conn)))
+    : vinagre_cache_prefs_get_string  ("spice-connection", "ssh-tunnel-host", NULL);
+  if (ssh_host)
+    gtk_entry_set_text (GTK_ENTRY (ssh_host_entry), ssh_host);
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check), ssh_host && *ssh_host);
+  g_free (ssh_host);
+
+  gtk_table_attach_defaults (table, box2, 1, 2, 3, 4);
+
+  /* Password */
+  box2 = gtk_hbox_new (FALSE, 4);
+
+  label = gtk_label_new_with_mnemonic (_("_Password:"));
+  gtk_box_pack_start (GTK_BOX (box2), label, FALSE, FALSE, 0);
+
+  p_entry = gtk_entry_new ();
+  /* Translators: This is the tooltip for the password field in a SPICE connection */
+  gtk_widget_set_tooltip_text (p_entry, _("Optional"));
+  g_object_set_data (G_OBJECT (box), "password_entry", p_entry);
+  gtk_box_pack_start (GTK_BOX (box2), p_entry, FALSE, FALSE, 0);
+  gtk_label_set_mnemonic_widget (GTK_LABEL (label), p_entry);
+  str = g_strdup (vinagre_cache_prefs_get_string  ("spice-connection", "password", ""));
+  gtk_entry_set_text (GTK_ENTRY (p_entry), str);
+  gtk_entry_set_activates_default (GTK_ENTRY (p_entry), TRUE);
+  g_free (str);
+  /* FIXME: how do you set the password here? gtk_table_attach_defaults (table, box2, 1, 2, 4, 5); */
+
+  gtk_box_pack_start (GTK_BOX (box), GTK_WIDGET (table), FALSE, FALSE, 0);
+  return box;
+}
+
+static GtkFileFilter *
+impl_get_file_filter (VinagreProtocol *plugin)
+{
+  GtkFileFilter *filter;
+
+  filter = gtk_file_filter_new ();
+  /* Translators: this is a pattern to open *.spice files in a open dialog. */
+  gtk_file_filter_set_name (filter, _("Spice Files"));
+  gtk_file_filter_add_pattern (filter, "*.spice");
+
+  return filter;
+}
+
+static void
+vinagre_spice_protocol_iface_init (VinagreProtocolInterface *iface)
+{
+  iface->get_protocol  = impl_get_protocol;
+  iface->get_public_description	 = impl_get_public_description;
+  iface->get_default_port = impl_get_default_port;
+  iface->get_connect_widget = impl_get_connect_widget;
+  iface->get_mdns_service  = impl_get_mdns_service;
+  iface->get_file_filter = impl_get_file_filter;
+  iface->new_connection = impl_new_connection;
+  iface->new_tab = impl_new_tab;
+  iface->new_connection_from_file = impl_new_connection_from_file;
+}
+
+static void
+vinagre_spice_plugin_class_finalize (VinagreSpicePluginClass *klass)
+{
+}
+
+static void
+vinagre_spice_plugin_class_init (VinagreSpicePluginClass *klass)
+{
+}
+
+G_MODULE_EXPORT void
+peas_register_types (PeasObjectModule *module)
+{
+  vinagre_spice_plugin_register_type (G_TYPE_MODULE (module));
+  peas_object_module_register_extension_type (module,
+					      VINAGRE_TYPE_PROTOCOL,
+					      VINAGRE_TYPE_SPICE_PLUGIN);
+}
+
+/* vim: set ts=8: */
diff --git a/plugins/spice/vinagre-spice-plugin.h b/plugins/spice/vinagre-spice-plugin.h
new file mode 100644
index 0000000..55bdf72
--- /dev/null
+++ b/plugins/spice/vinagre-spice-plugin.h
@@ -0,0 +1,62 @@
+/*
+ * vinagre-spice-plugin.h
+ * This file is part of vinagre
+ *
+ * Copyright (C) 2011 Red Hat, Inc.
+ * Author: Marc-Andre Lureau <marcandre lureau redhat com>
+ *
+ * vinagre-spice-plugin.h is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * vinagre-spice-plugin.h 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __VINAGRE_SPICE_PLUGIN_H__
+#define __VINAGRE_SPICE_PLUGIN_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include <libpeas/peas.h>
+
+G_BEGIN_DECLS
+
+/*
+ * Type checking and casting macros
+ */
+#define VINAGRE_TYPE_SPICE_PLUGIN                 (vinagre_spice_plugin_get_type ())
+#define VINAGRE_SPICE_PLUGIN(o)                   (G_TYPE_CHECK_INSTANCE_CAST ((o), VINAGRE_TYPE_SPICE_PLUGIN, VinagreSpicePlugin))
+#define VINAGRE_SPICE_PLUGIN_CLASS(k)             (G_TYPE_CHECK_CLASS_CAST((k), VINAGRE_TYPE_SPICE_PLUGIN, VinagreSpicePluginClass))
+#define VINAGRE_IS_SPICE_PLUGIN(o)                (G_TYPE_CHECK_INSTANCE_TYPE ((o), VINAGRE_TYPE_SPICE_PLUGIN))
+#define VINAGRE_IS_SPICE_PLUGIN_CLASS(k)          (G_TYPE_CHECK_CLASS_TYPE ((k), VINAGRE_TYPE_SPICE_PLUGIN))
+#define VINAGRE_SPICE_PLUGIN_GET_CLASS(o)         (G_TYPE_INSTANCE_GET_CLASS ((o), VINAGRE_TYPE_SPICE_PLUGIN, VinagreSpicePluginClass))
+
+typedef struct _VinagreSpicePlugin	VinagreSpicePlugin;
+typedef struct _VinagreSpicePluginClass	VinagreSpicePluginClass;
+
+struct _VinagreSpicePlugin
+{
+  PeasExtensionBase parent_instance;
+};
+
+struct _VinagreSpicePluginClass
+{
+  PeasExtensionBaseClass parent_class;
+};
+
+GType vinagre_spice_plugin_get_type (void) G_GNUC_CONST;
+
+G_MODULE_EXPORT void peas_register_types (PeasObjectModule *module);
+
+G_END_DECLS
+
+#endif /* __VINAGRE_SPICE_PLUGIN_H__ */
+
+/* vim: set ts=8: */
diff --git a/plugins/spice/vinagre-spice-tab.c b/plugins/spice/vinagre-spice-tab.c
new file mode 100644
index 0000000..921a3d6
--- /dev/null
+++ b/plugins/spice/vinagre-spice-tab.c
@@ -0,0 +1,753 @@
+/*
+ * vinagre-spice-tab.c
+ * SPICE Implementation for VinagreSpiceTab widget
+ * This file is part of vinagre
+ *
+ * Copyright (C) 2011 Red Hat, Inc.
+ * Author: Marc-Andre Lureau <marcandre lureau redhat com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n.h>
+#include <spice-client-glib-2.0/spice-session.h>
+#include <spice-client-glib-2.0/spice-audio.h>
+#include <spice-client-gtk-3.0/spice-widget.h>
+#include <gdk/gdkkeysyms.h>
+
+#include <vinagre/vinagre-utils.h>
+#include <vinagre/vinagre-prefs.h>
+
+#include "vinagre-spice-tab.h"
+#include "vinagre-spice-connection.h"
+#include "vinagre-spice-tunnel.h"
+
+#define VINAGRE_SPICE_TAB_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), VINAGRE_TYPE_SPICE_TAB, VinagreSpiceTabPrivate))
+
+typedef struct _VinagreSpiceDisplay
+{
+  GtkWidget  *display;
+} VinagreSpiceDisplay;
+
+struct _VinagreSpiceTabPrivate
+{
+  SpiceSession *spice;
+  SpiceAudio *audio;
+  GtkWidget  *display, *align;
+  gboolean   mouse_grabbed;
+  GSList     *connected_actions, *initialized_actions;
+  GtkWidget  *viewonly_button, *scaling_button;
+  GtkAction  *scaling_action, *viewonly_action, *original_size_action, *resize_guest_action, *auto_clipboard_action;
+  gulong     signal_align;
+  VinagreSpiceDisplay *wins[4]; /* TODO: handle multi-display */
+};
+
+G_DEFINE_TYPE (VinagreSpiceTab, vinagre_spice_tab, VINAGRE_TYPE_TAB)
+
+/* Properties */
+enum
+{
+  PROP_0,
+};
+
+static void open_spice (VinagreSpiceTab *spice_tab);
+static void setup_toolbar (VinagreSpiceTab *spice_tab);
+
+static void
+vinagre_spice_tab_get_property (GObject	   *object,
+				guint	    prop_id,
+				GValue	   *value,
+				GParamSpec *pspec)
+{
+  switch (prop_id)
+    {
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+view_scaling_cb (GtkAction *action, VinagreSpiceTab *spice_tab)
+{
+  vinagre_spice_tab_set_scaling (spice_tab,
+				 gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
+}
+
+static void
+view_resize_guest_cb (GtkAction *action, VinagreSpiceTab *spice_tab)
+{
+  vinagre_spice_tab_set_resize_guest (spice_tab,
+				      gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
+}
+
+static void
+view_auto_clipboard_cb (GtkAction *action, VinagreSpiceTab *spice_tab)
+{
+  vinagre_spice_tab_set_auto_clipboard (spice_tab,
+					gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
+}
+
+static void
+view_viewonly_cb (GtkAction *action, VinagreSpiceTab *spice_tab)
+{
+  vinagre_spice_tab_set_viewonly (spice_tab,
+				  gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
+}
+
+static void
+send_ctrlaltdel_cb (GtkAction *action, VinagreSpiceTab *spice_tab)
+{
+  vinagre_spice_tab_send_ctrlaltdel (spice_tab);
+}
+
+const static GSList *
+spice_get_connected_actions (VinagreTab *tab)
+{
+  VinagreSpiceTab *spice_tab = VINAGRE_SPICE_TAB (tab);
+
+  return spice_tab->priv->connected_actions;
+}
+
+const static GSList *
+spice_get_initialized_actions (VinagreTab *tab)
+{
+  VinagreSpiceTab *spice_tab = VINAGRE_SPICE_TAB (tab);
+
+  return spice_tab->priv->initialized_actions;
+}
+
+static gchar *
+spice_tab_get_tooltip (VinagreTab *tab)
+{
+  VinagreConnection *conn = vinagre_tab_get_conn (tab);
+
+  return  g_markup_printf_escaped ("<b>%s</b> %s\n\n"
+				   "<b>%s</b> %d\n",
+				   _("Host:"), vinagre_connection_get_host (conn),
+				   _("Port:"), vinagre_connection_get_port (conn));
+}
+
+static void
+vinagre_spice_tab_finalize (GObject *object)
+{
+  G_OBJECT_CLASS (vinagre_spice_tab_parent_class)->finalize (object);
+}
+
+static void
+vinagre_spice_tab_dispose (GObject *object)
+{
+  VinagreSpiceTab *spice_tab = VINAGRE_SPICE_TAB (object);
+
+  if (spice_tab->priv->connected_actions)
+    {
+      vinagre_tab_free_actions (spice_tab->priv->connected_actions);
+      spice_tab->priv->connected_actions = NULL;
+    }
+
+  if (spice_tab->priv->initialized_actions)
+    {
+      vinagre_tab_free_actions (spice_tab->priv->initialized_actions);
+      spice_tab->priv->initialized_actions = NULL;
+    }
+
+  if (spice_tab->priv->audio)
+    {
+      g_object_unref (spice_tab->priv->audio);
+      spice_tab->priv->audio = NULL;
+    }
+
+  if (spice_tab->priv->spice)
+    {
+      spice_session_disconnect (spice_tab->priv->spice);
+      g_object_unref (spice_tab->priv->spice);
+      spice_tab->priv->spice = NULL;
+    }
+
+  G_OBJECT_CLASS (vinagre_spice_tab_parent_class)->dispose (object);
+}
+
+static void
+vinagre_spice_tab_constructed (GObject *object)
+{
+  VinagreSpiceTab *spice_tab = VINAGRE_SPICE_TAB (object);
+
+  if (G_OBJECT_CLASS (vinagre_spice_tab_parent_class)->constructed)
+    G_OBJECT_CLASS (vinagre_spice_tab_parent_class)->constructed (object);
+
+  setup_toolbar (spice_tab);
+  open_spice (spice_tab);
+}
+
+static void
+vinagre_spice_tab_class_init (VinagreSpiceTabClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  VinagreTabClass* tab_class = VINAGRE_TAB_CLASS (klass);
+
+  object_class->finalize = vinagre_spice_tab_finalize;
+  object_class->dispose = vinagre_spice_tab_dispose;
+  object_class->get_property = vinagre_spice_tab_get_property;
+  object_class->constructed = vinagre_spice_tab_constructed;
+
+  tab_class->impl_get_tooltip = spice_tab_get_tooltip;
+  tab_class->impl_get_connected_actions = spice_get_connected_actions;
+  tab_class->impl_get_initialized_actions = spice_get_initialized_actions;
+
+  g_type_class_add_private (object_class, sizeof (VinagreSpiceTabPrivate));
+}
+
+static gboolean
+idle_close (VinagreTab *tab)
+{
+  vinagre_notebook_close_tab (vinagre_tab_get_notebook (tab), tab);
+  return FALSE;
+}
+
+static void
+open_spice (VinagreSpiceTab *spice_tab)
+{
+  gchar	     *host, *port_str, *ssh_tunnel_host;
+  gint	     port, fd;
+  gboolean   scaling, success;
+  GError     *error;
+  SpiceSession *spice = spice_tab->priv->spice;
+  VinagreTab *tab = VINAGRE_TAB (spice_tab);
+  GtkWindow  *window = GTK_WINDOW (vinagre_tab_get_window (tab));
+  VinagreConnection *conn = vinagre_tab_get_conn (tab);
+
+  success = TRUE;
+  error = NULL;
+
+  g_object_get (conn,
+		"port", &port,
+		"host", &host,
+		"scaling", &scaling,
+		"fd", &fd,
+		"ssh-tunnel-host", &ssh_tunnel_host,
+		NULL);
+
+  port_str = g_strdup_printf ("%d", port);
+
+  if (fd > 0)
+    success = spice_session_open_fd (spice, fd);
+  else
+    {
+      if (ssh_tunnel_host && *ssh_tunnel_host)
+	if (!vinagre_spice_tunnel_create (window, &host, &port_str, ssh_tunnel_host, &error))
+	  {
+	    success = FALSE;
+	    vinagre_utils_show_error (_("Error creating the SSH tunnel"),
+				      error ? error->message : _("Unknown reason"),
+				      window);
+	    goto out;
+	  }
+      g_object_set (spice, "host", host, "port", port_str,
+		    "password", vinagre_connection_get_password (conn),
+		    NULL);
+      success = spice_session_connect (spice);
+    }
+
+  if (!success)
+    vinagre_utils_show_error (_("Error connecting to host."),
+			      error ? error->message : _("Unknown reason"),
+			      window);
+
+ out:
+  g_free (port_str);
+  g_free (host);
+  g_free (ssh_tunnel_host);
+  g_clear_error (&error);
+
+  if (!success)
+    g_idle_add ((GSourceFunc)idle_close, spice_tab);
+}
+
+static void
+spice_main_channel_event_cb(SpiceChannel *channel, SpiceChannelEvent event,
+			    VinagreSpiceTab *spice_tab)
+{
+  switch (event) {
+  case SPICE_CHANNEL_OPENED:
+    g_signal_emit_by_name (G_OBJECT (spice_tab), "tab-connected");
+    break;
+  case SPICE_CHANNEL_CLOSED:
+    g_signal_emit_by_name (G_OBJECT (spice_tab), "tab-disconnected");
+    break;
+  case SPICE_CHANNEL_ERROR_AUTH: {
+    VinagreTab *tab = VINAGRE_TAB (spice_tab);
+    VinagreConnection *conn = vinagre_tab_get_conn (tab);
+    gchar *name = vinagre_connection_get_best_name (conn), *password = NULL;
+    GtkWindow *window = GTK_WINDOW (vinagre_tab_get_window (tab));
+    gboolean save_in_keyring = FALSE;
+
+    vinagre_tab_remove_credentials_from_keyring (VINAGRE_TAB (tab));
+    if (!vinagre_utils_ask_credential (window,
+				       "SPICE",
+				       name,
+				       FALSE,
+				       TRUE,
+				       8,
+				       NULL,
+				       &password,
+				       &save_in_keyring))
+      {
+	vinagre_tab_remove_from_notebook (tab);
+	goto out;
+      }
+    vinagre_connection_set_password (conn, password);
+    vinagre_tab_set_save_credentials (tab, save_in_keyring);
+
+    g_object_set (spice_tab->priv->spice, "password", password, NULL);
+    spice_session_connect (spice_tab->priv->spice);
+
+    out:
+    g_free (password);
+    break;
+  }
+  case SPICE_CHANNEL_ERROR_IO:
+  case SPICE_CHANNEL_ERROR_TLS:
+  case SPICE_CHANNEL_ERROR_LINK:
+  case SPICE_CHANNEL_ERROR_CONNECT:
+    g_signal_emit_by_name (G_OBJECT (spice_tab), "tab-disconnected");
+    break;
+  default:
+    g_warning("unhandled main channel event: %d", event);
+    break;
+  }
+}
+
+static void
+spice_mouse_grab_cb(GtkWidget *widget, gint grabbed, VinagreSpiceTab *spice_tab)
+{
+  spice_tab->priv->mouse_grabbed = grabbed;
+}
+
+static VinagreSpiceDisplay *
+create_spice_display (VinagreSpiceTab *spice_tab, int id)
+{
+  VinagreSpiceDisplay *d;
+  GtkLabel *label;
+  gchar	   *name;
+  gboolean scaling, resize_guest, view_only, fullscreen, auto_clipboard;
+  VinagreTab *tab = VINAGRE_TAB (spice_tab);
+  VinagreConnection *conn = vinagre_tab_get_conn (tab);
+
+  d = g_new0(VinagreSpiceDisplay, 1);
+
+  /* Create the display widget */
+  spice_tab->priv->display = d->display = GTK_WIDGET (spice_display_new (spice_tab->priv->spice, id));
+
+  vinagre_tab_add_view (tab, d->display);
+  vinagre_tab_set_has_screenshot (tab, TRUE);
+
+  g_signal_connect (spice_tab->priv->display, "mouse-grab",
+		    G_CALLBACK (spice_mouse_grab_cb), spice_tab);
+
+  g_object_get (conn,
+		"fullscreen", &fullscreen,
+		"resize-guest", &resize_guest,
+		"auto-clipboard", &auto_clipboard,
+		"scaling", &scaling,
+		"view-only", &view_only,
+		NULL);
+
+  gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (spice_tab->priv->scaling_action), scaling);
+  gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (spice_tab->priv->resize_guest_action), resize_guest);
+  gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (spice_tab->priv->auto_clipboard_action), auto_clipboard);
+  gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (spice_tab->priv->viewonly_action), view_only);
+  g_object_set (d->display,
+		"grab-keyboard", TRUE,
+		"grab-mouse", TRUE,
+		"resize-guest", resize_guest,
+		"auto-clipboard", auto_clipboard,
+		"scaling", scaling,
+		NULL);
+  /* TODO: add view-only here when spice-gtk ready */
+
+  g_object_get (spice_tab->priv->spice, "uri", &name, NULL); /* TODO: a better friendly name? */
+  vinagre_spice_connection_set_desktop_name (VINAGRE_SPICE_CONNECTION (conn), name);
+  g_free (name);
+
+  name = vinagre_connection_get_best_name (conn);
+  label = g_object_get_data (G_OBJECT (tab), "label");
+  g_return_val_if_fail (label != NULL, d);
+  gtk_label_set_label (label, name);
+  g_free (name);
+
+  vinagre_tab_save_credentials_in_keyring (tab);
+  vinagre_tab_add_recent_used (tab);
+  vinagre_tab_set_state (tab, VINAGRE_TAB_STATE_CONNECTED);
+
+  g_signal_emit_by_name (G_OBJECT (tab), "tab-initialized");
+
+  gtk_widget_show_all (GTK_WIDGET (tab));
+
+  return d;
+}
+
+static void
+destroy_spice_display (VinagreSpiceTab *tab, VinagreSpiceDisplay *display)
+{
+}
+
+static void
+spice_channel_new_cb (SpiceSession *s, SpiceChannel *channel, VinagreSpiceTab *tab)
+{
+  int id;
+
+  g_object_get (channel, "channel-id", &id, NULL);
+  g_object_ref (tab);
+
+  if (SPICE_IS_MAIN_CHANNEL (channel)) {
+    g_signal_connect (channel, "channel-event",
+		      G_CALLBACK (spice_main_channel_event_cb), tab);
+  }
+
+  if (SPICE_IS_DISPLAY_CHANNEL (channel)) {
+    if (id >= G_N_ELEMENTS (tab->priv->wins))
+      return;
+    if (tab->priv->wins[id] != NULL)
+      return;
+    tab->priv->wins[id] = create_spice_display (tab, id);
+  }
+
+  if (SPICE_IS_PLAYBACK_CHANNEL (channel) ||
+      SPICE_IS_RECORD_CHANNEL (channel)) {
+    if (tab->priv->audio != NULL)
+      return;
+    tab->priv->audio = spice_audio_new (s, NULL, NULL);
+  }
+}
+
+static void
+spice_channel_destroy_cb (SpiceSession *s, SpiceChannel *channel, VinagreSpiceTab *tab)
+{
+  int id;
+
+  g_object_get (channel, "channel-id", &id, NULL);
+  g_object_unref (tab);
+
+  if (SPICE_IS_DISPLAY_CHANNEL (channel)) {
+    if (id >= G_N_ELEMENTS (tab->priv->wins))
+      return;
+    if (tab->priv->wins[id] == NULL)
+      return;
+    destroy_spice_display (tab, tab->priv->wins[id]);
+    tab->priv->wins[id] = NULL;
+  }
+}
+
+static GSList *
+create_connected_actions (VinagreSpiceTab *tab)
+{
+  GSList *list = NULL;
+  VinagreTabUiAction *a;
+
+  /* View->Scaling */
+  a = g_new (VinagreTabUiAction, 1);
+  a->paths = g_new (gchar *, 3);
+  a->paths[0] = g_strdup ("/MenuBar/ViewMenu");
+  a->paths[1] = g_strdup ("/ToolBar");
+  a->paths[2] = NULL;
+  a->action = GTK_ACTION (gtk_toggle_action_new ("SPICEViewScaling",
+						 _("S_caling"),
+						 _("Fits the remote screen into the current window size"),
+						 "zoom-fit-best"));
+  gtk_action_set_icon_name (a->action, "zoom-fit-best");
+  g_signal_connect (a->action, "activate", G_CALLBACK (view_scaling_cb), tab);
+  list = g_slist_append (list, a);
+  tab->priv->scaling_action = a->action;
+
+  /* View->Resize Guest */
+  a = g_new (VinagreTabUiAction, 1);
+  a->paths = g_new (gchar *, 2);
+  a->paths[0] = g_strdup ("/MenuBar/ViewMenu");
+  a->paths[1] = NULL;
+  a->action = GTK_ACTION (gtk_toggle_action_new ("SPICEViewResizeGuest",
+						 _("_Resize guest"),
+						 _("Resize the screen guest to best fit"),
+						 NULL));
+  g_signal_connect (a->action, "activate", G_CALLBACK (view_resize_guest_cb), tab);
+  list = g_slist_append (list, a);
+  tab->priv->resize_guest_action = a->action;
+
+  /* View->Share clipboard */
+  a = g_new (VinagreTabUiAction, 1);
+  a->paths = g_new (gchar *, 2);
+  a->paths[0] = g_strdup ("/MenuBar/ViewMenu");
+  a->paths[1] = NULL;
+  a->action = GTK_ACTION (gtk_toggle_action_new ("SPICEViewAutoClipboard",
+						 _("_Share clipboard"),
+						 _("Automatically share clipboard between client and guest"),
+						 NULL));
+  g_signal_connect (a->action, "activate", G_CALLBACK (view_auto_clipboard_cb), tab);
+  list = g_slist_append (list, a);
+  tab->priv->auto_clipboard_action = a->action;
+
+  /* View->View Only TODO: not ready in spice-gtk yet */
+  a = g_new (VinagreTabUiAction, 1);
+  a->paths = g_new (gchar *, 3);
+  a->paths[0] = g_strdup ("/MenuBar/ViewMenu");
+  a->paths[1] = g_strdup ("/ToolBar");
+  a->paths[2] = NULL;
+  a->action = GTK_ACTION (gtk_toggle_action_new ("SPICEViewViewOnly",
+						 _("_View only"),
+						 _("Does not send mouse and keyboard events"),
+						 "emblem-readonly"));
+  gtk_action_set_icon_name (a->action, "emblem-readonly");
+  g_signal_connect (a->action, "activate", G_CALLBACK (view_viewonly_cb), tab);
+  /* list = g_slist_append (list, a); */
+  tab->priv->viewonly_action = a->action;
+
+  return list;
+}
+
+static GSList *
+create_initialized_actions (VinagreSpiceTab *tab)
+{
+  GSList *list = NULL;
+  VinagreTabUiAction *a;
+
+  /* Machine->Send CTRL-ALT-DEL */
+  a = g_new (VinagreTabUiAction, 1);
+  a->paths = g_new (gchar *, 3);
+  a->paths[0] = g_strdup ("/MenuBar/MachineMenu/MachineOps_1");
+  a->paths[1] = g_strdup ("/ToolBar");
+  a->paths[2] = NULL;
+  a->action = gtk_action_new ("SPICEMachineSendCtrlAltDel",
+			      _("_Send Ctrl-Alt-Del"),
+			      _("Sends Ctrl+Alt+Del to the remote machine"),
+			      "preferences-desktop-keyboard-shortcuts");
+  gtk_action_set_is_important (a->action, TRUE);
+  gtk_action_set_icon_name (a->action, "preferences-desktop-keyboard-shortcuts");
+  g_signal_connect (a->action, "activate", G_CALLBACK (send_ctrlaltdel_cb), tab);
+  list = g_slist_append (list, a);
+
+  return list;
+}
+
+static void
+viewonly_button_clicked (GtkToggleToolButton *button,
+			 VinagreSpiceTab     *spice_tab)
+{
+  vinagre_spice_tab_set_viewonly (spice_tab, gtk_toggle_tool_button_get_active (button));
+
+  gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (spice_tab->priv->viewonly_action),
+				vinagre_spice_tab_get_viewonly (spice_tab));
+}
+
+static void
+scaling_button_clicked (GtkToggleToolButton *button,
+			VinagreSpiceTab	    *spice_tab)
+{
+  if (!vinagre_spice_tab_set_scaling (spice_tab, gtk_toggle_tool_button_get_active (button)))
+    gtk_toggle_tool_button_set_active (button, FALSE);
+
+  gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (spice_tab->priv->scaling_action),
+				vinagre_spice_tab_get_scaling (spice_tab));
+}
+
+static void
+cad_button_clicked (GtkToolButton *button,
+		    VinagreSpiceTab *spice_tab)
+{
+  vinagre_spice_tab_send_ctrlaltdel (spice_tab);
+}
+
+static void
+setup_toolbar (VinagreSpiceTab *spice_tab)
+{
+  GtkWidget *toolbar = vinagre_tab_get_toolbar (VINAGRE_TAB (spice_tab));
+  GtkWidget *button;
+
+  /* Space */
+  button = GTK_WIDGET (gtk_separator_tool_item_new ());
+  gtk_tool_item_set_expand (GTK_TOOL_ITEM (button), TRUE);
+  gtk_widget_show (GTK_WIDGET (button));
+  gtk_toolbar_insert (GTK_TOOLBAR (toolbar), GTK_TOOL_ITEM (button), -1);
+
+  /* Scaling */
+  button = GTK_WIDGET (gtk_toggle_tool_button_new ());
+  gtk_tool_button_set_label (GTK_TOOL_BUTTON (button), _("Scaling"));
+  gtk_tool_item_set_tooltip_text (GTK_TOOL_ITEM (button), _("Scaling"));
+  gtk_tool_button_set_icon_name (GTK_TOOL_BUTTON (button), "zoom-fit-best");
+  gtk_widget_show (GTK_WIDGET (button));
+  gtk_toolbar_insert (GTK_TOOLBAR (toolbar), GTK_TOOL_ITEM (button), -1);
+  g_signal_connect (button, "toggled", G_CALLBACK (scaling_button_clicked), spice_tab);
+  spice_tab->priv->scaling_button = button;
+
+  /* Read only */
+  button = GTK_WIDGET (gtk_toggle_tool_button_new ());
+  gtk_tool_button_set_label (GTK_TOOL_BUTTON (button), _("Read only"));
+  gtk_tool_item_set_tooltip_text (GTK_TOOL_ITEM (button), _("Read only"));
+  gtk_tool_button_set_icon_name (GTK_TOOL_BUTTON (button), "emblem-readonly");
+  gtk_widget_show (GTK_WIDGET (button));
+  gtk_toolbar_insert (GTK_TOOLBAR (toolbar), GTK_TOOL_ITEM (button), -1);
+  g_signal_connect (button, "toggled", G_CALLBACK (viewonly_button_clicked), spice_tab);
+  spice_tab->priv->viewonly_button = button;
+
+  /* Send Ctrl-alt-del */
+  button = GTK_WIDGET (gtk_tool_button_new (NULL, _("Send Ctrl-Alt-Del")));
+  gtk_tool_button_set_icon_name (GTK_TOOL_BUTTON (button), "preferences-desktop-keyboard-shortcuts");
+  gtk_tool_item_set_tooltip_text (GTK_TOOL_ITEM (button), _("Send Ctrl-Alt-Del"));
+  g_signal_connect (button, "clicked", G_CALLBACK (cad_button_clicked), spice_tab);
+  gtk_widget_show (GTK_WIDGET (button));
+  gtk_toolbar_insert (GTK_TOOLBAR (toolbar), GTK_TOOL_ITEM (button), -1);
+}
+
+static void
+vinagre_spice_tab_init (VinagreSpiceTab *spice_tab)
+{
+  SpiceSession *spice;
+
+  spice_tab->priv = VINAGRE_SPICE_TAB_GET_PRIVATE (spice_tab);
+  spice_tab->priv->connected_actions = create_connected_actions (spice_tab);
+  spice_tab->priv->initialized_actions = create_initialized_actions (spice_tab);
+
+  /* Create the spice widget */
+  spice = spice_tab->priv->spice = spice_session_new ();
+  g_signal_connect(spice, "channel-new",
+		   G_CALLBACK(spice_channel_new_cb), spice_tab);
+  g_signal_connect(spice, "channel-destroy",
+		   G_CALLBACK(spice_channel_destroy_cb), spice_tab);
+}
+
+GtkWidget *
+vinagre_spice_tab_new (VinagreConnection *conn,
+		       VinagreWindow	 *window)
+{
+  return GTK_WIDGET (g_object_new (VINAGRE_TYPE_SPICE_TAB,
+				   "conn", conn,
+				   "window", window,
+				   NULL));
+}
+
+void
+vinagre_spice_tab_send_ctrlaltdel (VinagreSpiceTab *tab)
+{
+  guint keys[] = { GDK_KEY_Control_L, GDK_KEY_Alt_L, GDK_KEY_Delete };
+
+  g_return_if_fail (VINAGRE_IS_SPICE_TAB (tab));
+
+  spice_display_send_keys (SPICE_DISPLAY (tab->priv->display), keys, sizeof (keys) / sizeof (keys[0]), SPICE_DISPLAY_KEY_EVENT_CLICK);
+}
+
+gboolean
+vinagre_spice_tab_set_scaling (VinagreSpiceTab *tab, gboolean active)
+{
+  gboolean scaling;
+
+  g_return_val_if_fail (VINAGRE_IS_SPICE_TAB (tab), FALSE);
+
+  g_object_get (tab->priv->display, "scaling", &scaling, NULL);
+  if (scaling == active)
+    return TRUE;
+
+  g_object_set (tab->priv->display, "scaling", active, NULL);
+
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (tab->priv->scaling_button),
+				     active);
+
+  return TRUE;
+}
+
+gboolean
+vinagre_spice_tab_get_scaling (VinagreSpiceTab *tab)
+{
+  gboolean scaling;
+
+  g_return_val_if_fail (VINAGRE_IS_SPICE_TAB (tab), FALSE);
+
+  g_object_get (tab->priv->display, "scaling", &scaling, NULL);
+
+  return scaling;
+}
+
+void
+vinagre_spice_tab_set_viewonly (VinagreSpiceTab *tab, gboolean active)
+{
+  g_return_if_fail (VINAGRE_IS_SPICE_TAB (tab));
+
+#if 0
+  g_object_set (tab->priv->display, "read-only", active, NULL); /* only in future version of spice-gtk */
+#endif
+
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (tab->priv->viewonly_button),
+				     active);
+}
+
+gboolean
+vinagre_spice_tab_get_viewonly (VinagreSpiceTab *tab)
+{
+  gboolean active = FALSE;
+
+  g_return_val_if_fail (VINAGRE_IS_SPICE_TAB (tab), FALSE);
+#if 0
+  g_object_get (tab->priv->display, "read-only", &active, NULL);  /* only in future version of spice-gtk */
+#endif
+  return active;
+}
+
+gboolean
+vinagre_spice_tab_set_resize_guest (VinagreSpiceTab *tab, gboolean active)
+{
+  g_return_val_if_fail (VINAGRE_IS_SPICE_TAB (tab), FALSE);
+
+  g_object_set (tab->priv->display, "resize-guest", active, NULL);
+
+  return TRUE;
+}
+
+gboolean
+vinagre_spice_tab_get_resize_guest (VinagreSpiceTab *tab)
+{
+  gboolean active;
+
+  g_return_val_if_fail (VINAGRE_IS_SPICE_TAB (tab), FALSE);
+
+  g_object_get (tab->priv->display, "resize-guest", &active, NULL);
+
+  return active;
+}
+
+void
+vinagre_spice_tab_set_auto_clipboard (VinagreSpiceTab *tab, gboolean active)
+{
+  g_return_if_fail (VINAGRE_IS_SPICE_TAB (tab));
+
+  g_object_set (tab->priv->display, "auto-clipboard", active, NULL);
+}
+
+gboolean
+vinagre_spice_tab_get_auto_clipboard (VinagreSpiceTab *tab)
+{
+  gboolean active;
+
+  g_return_val_if_fail (VINAGRE_IS_SPICE_TAB (tab), FALSE);
+
+  g_object_get (tab->priv->display, "auto-clipboard", &active, NULL);
+
+  return active;
+}
+
+gboolean
+vinagre_spice_tab_is_mouse_grab (VinagreSpiceTab *tab)
+{
+  g_return_val_if_fail (VINAGRE_IS_SPICE_TAB (tab), FALSE);
+
+  return tab->priv->mouse_grabbed;
+}
+
+/* vim: set ts=8: */
diff --git a/plugins/spice/vinagre-spice-tab.h b/plugins/spice/vinagre-spice-tab.h
new file mode 100644
index 0000000..5d3fffa
--- /dev/null
+++ b/plugins/spice/vinagre-spice-tab.h
@@ -0,0 +1,76 @@
+/*
+ * vinagre-spice-tab.h
+ * SPICE Tab
+ * This file is part of vinagre
+ *
+ * Copyright (C) 2011 Red Hat, Inc.
+ * Author: Marc-Andre Lureau <marcandre lureau redhat com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __VINAGRE_SPICE_TAB_H__
+#define __VINAGRE_SPICE_TAB_H__
+
+#include <vinagre/vinagre-tab.h>
+
+G_BEGIN_DECLS
+
+#define VINAGRE_TYPE_SPICE_TAB              (vinagre_spice_tab_get_type())
+#define VINAGRE_SPICE_TAB(obj)              (G_TYPE_CHECK_INSTANCE_CAST((obj), VINAGRE_TYPE_SPICE_TAB, VinagreSpiceTab))
+#define VINAGRE_SPICE_TAB_CONST(obj)        (G_TYPE_CHECK_INSTANCE_CAST((obj), VINAGRE_TYPE_SPICE_TAB, VinagreSpiceTab const))
+#define VINAGRE_SPICE_TAB_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST((klass), VINAGRE_TYPE_SPICE_TAB, VinagreSpiceTabClass))
+#define VINAGRE_IS_SPICE_TAB(obj)           (G_TYPE_CHECK_INSTANCE_TYPE((obj), VINAGRE_TYPE_SPICE_TAB))
+#define VINAGRE_IS_SPICE_TAB_CLASS(klass)...(G_TYPE_CHECK_CLASS_TYPE ((klass), VINAGRE_TYPE_SPICE_TAB))
+#define VINAGRE_SPICE_TAB_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS((obj), VINAGRE_TYPE_SPICE_TAB, VinagreSpiceTabClass))
+
+typedef struct _VinagreSpiceTabPrivate VinagreSpiceTabPrivate;
+typedef struct _VinagreSpiceTab        VinagreSpiceTab;
+typedef struct _VinagreSpiceTabClass   VinagreSpiceTabClass;
+
+
+struct _VinagreSpiceTab
+{
+  VinagreTab tab;
+  VinagreSpiceTabPrivate *priv;
+};
+
+struct _VinagreSpiceTabClass
+{
+  VinagreTabClass parent_class;
+};
+
+GType		vinagre_spice_tab_get_type		(void) G_GNUC_CONST;
+
+GtkWidget *	vinagre_spice_tab_new			(VinagreConnection *conn,
+							 VinagreWindow	   *window);
+
+void		vinagre_spice_tab_send_ctrlaltdel	(VinagreSpiceTab *tab);
+void		vinagre_spice_tab_paste_text		(VinagreSpiceTab *tab,
+							 const gchar   *text);
+
+gboolean	vinagre_spice_tab_set_scaling		(VinagreSpiceTab *tab, gboolean active);
+gboolean	vinagre_spice_tab_get_scaling		(VinagreSpiceTab *tab);
+gboolean	vinagre_spice_tab_set_resize_guest	(VinagreSpiceTab *tab, gboolean active);
+gboolean	vinagre_spice_tab_get_resize_guest	(VinagreSpiceTab *tab);
+void		vinagre_spice_tab_set_viewonly		(VinagreSpiceTab *tab, gboolean active);
+gboolean	vinagre_spice_tab_get_viewonly		(VinagreSpiceTab *tab);
+void		vinagre_spice_tab_set_auto_clipboard	(VinagreSpiceTab *tab, gboolean active);
+gboolean	vinagre_spice_tab_get_auto_clipboard	(VinagreSpiceTab *tab);
+
+gboolean	vinagre_spice_tab_is_mouse_grab		(VinagreSpiceTab *tab);
+G_END_DECLS
+
+#endif  /* __VINAGRE_SPICE_TAB_H__  */
+/* vim: set ts=8: */
diff --git a/plugins/spice/vinagre-spice-tunnel.c b/plugins/spice/vinagre-spice-tunnel.c
new file mode 100644
index 0000000..2759651
--- /dev/null
+++ b/plugins/spice/vinagre-spice-tunnel.c
@@ -0,0 +1,155 @@
+/*
+ * vinagre-spice-tunnel.c
+ * SSH Tunneling for Vinagre
+ * This file is part of vinagre
+ *
+ * Copyright (C) 2009 - Jonh Wendell <wendell bani com br>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <glib/gi18n.h>
+
+#include <vinagre/vinagre-ssh.h>
+#include "vinagre-spice-tunnel.h"
+
+#define TUNNEL_PORT_OFFSET 5500
+
+static int
+find_free_port (void)
+{
+  int sock, port;
+  struct sockaddr_in6 addr;
+
+  memset (&addr, 0, sizeof (addr));
+  addr.sin6_family = AF_INET6;
+  addr.sin6_addr = in6addr_any;
+
+  sock = socket (AF_INET6, SOCK_STREAM, IPPROTO_TCP);
+  if (sock < 0)
+    return 0;
+
+  for (port = TUNNEL_PORT_OFFSET + 99; port > TUNNEL_PORT_OFFSET; port--)
+    {
+      addr.sin6_port = htons (port);
+      if (bind (sock, (struct sockaddr *)&addr, sizeof (addr)) == 0)
+	{
+	  close (sock);
+	  return port;
+	}
+    }
+
+  close (sock);
+  return 0;
+}
+
+static void
+split_gateway (const gchar *gateway, gchar **host, gint *port)
+{
+  if (g_strrstr (gateway, ":") == NULL)
+    {
+      *host = g_strdup (gateway);
+      *port = 22;
+    }
+  else
+    {
+      gchar **server = g_strsplit (gateway, ":", 2);
+      *host = g_strdup (server[0]);
+      *port = server[1] ? atoi (server[1]) : 22;
+      g_strfreev (server);
+    }
+}
+
+
+gboolean
+vinagre_spice_tunnel_create (GtkWindow *parent,
+			   gchar **original_host,
+			   gchar **original_port,
+			   gchar *gateway,
+			   GError **error)
+{
+  int local_port, gateway_port;
+  gchar **tunnel_str, **command_str, *gateway_host;
+
+  local_port = find_free_port ();
+  if (local_port == 0)
+    {
+      g_set_error (error,
+		   VINAGRE_SPICE_TUNNEL_ERROR,
+		   VINAGRE_SPICE_TUNNEL_ERROR_NO_FREE_PORT,
+		   _("Unable to find a free TCP port"));
+      return FALSE;
+    }
+
+  tunnel_str = g_new (gchar *, 4);
+  tunnel_str[0] = g_strdup ("-f");
+  tunnel_str[1] = g_strdup ("-L");
+  tunnel_str[2] = g_strdup_printf ("%d:%s:%s",
+				   local_port,
+				   *original_host,
+				   *original_port);
+  tunnel_str[3] = NULL;
+
+  command_str = g_new (gchar *, 5);
+  command_str[0] = g_strdup ("echo");
+  command_str[1] = g_strdup_printf ("%s;", VINAGRE_SSH_CHECK);
+  command_str[2] = g_strdup ("sleep");
+  command_str[3] = g_strdup ("15");
+  command_str[4] = NULL;
+
+  split_gateway (gateway, &gateway_host, &gateway_port);
+
+  if (!vinagre_ssh_connect (parent,
+			    gateway_host,
+			    gateway_port,
+			    NULL,
+			    tunnel_str,
+			    command_str,
+			    NULL,
+			    error))
+    {
+      g_strfreev (tunnel_str);
+      g_strfreev (command_str);
+      g_free (gateway_host);
+      return FALSE;
+    }
+
+  g_strfreev (tunnel_str);
+  g_strfreev (command_str);
+  g_free (gateway_host);
+  g_free (*original_host);
+  *original_host = g_strdup ("localhost");
+  g_free (*original_port);
+  *original_port = g_strdup_printf ("%d", local_port);
+
+  return TRUE;
+}
+
+GQuark
+vinagre_spice_tunnel_error_quark (void)
+{
+  static GQuark quark = 0;
+
+  if (!quark)
+    quark = g_quark_from_string ("vinagre_spice_tunnel_error");
+
+  return quark;
+}
+
+/* vim: set ts=8: */
diff --git a/plugins/spice/vinagre-spice-tunnel.h b/plugins/spice/vinagre-spice-tunnel.h
new file mode 100644
index 0000000..4327f82
--- /dev/null
+++ b/plugins/spice/vinagre-spice-tunnel.h
@@ -0,0 +1,46 @@
+/*
+ * vinagre-spice-tunnel.h
+ * SSH Tunneling for Vinagre
+ * This file is part of vinagre
+ *
+ * Copyright (C) 2009 - Jonh Wendell <wendell bani com br>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __VINAGRE_SPICE_TUNNEL_H__
+#define __VINAGRE_SPICE_TUNNEL_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+  VINAGRE_SPICE_TUNNEL_ERROR_NO_FREE_PORT = 1
+} VinagreSpiceTunnelError;
+
+#define VINAGRE_SPICE_TUNNEL_ERROR vinagre_spice_tunnel_error_quark()
+GQuark vinagre_spice_tunnel_error_quark (void);
+
+gboolean vinagre_spice_tunnel_create (GtkWindow *parent,
+				    gchar **original_host,
+				    gchar **original_port,
+				    gchar *gateway,
+				    GError **error);
+
+G_END_DECLS
+
+#endif  /* __VINAGRE_SPICE_TUNNEL_H__  */
+/* vim: set ts=8: */



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