[virt-tools-list] [PATCH 07/12] Add spice controller support in remote-viewer

Marc-André Lureau marcandre.lureau at gmail.com
Tue Dec 13 19:35:05 UTC 2011


Usage is simply "remote-viewer --spice-controller"
---
 configure.ac                    |    3 +-
 src/remote-viewer-main.c        |   20 ++-
 src/remote-viewer.c             |  374 ++++++++++++++++++++++++++++++++++++--
 src/remote-viewer.h             |    4 +-
 src/virt-viewer-app.c           |    7 +
 src/virt-viewer-app.h           |    1 +
 src/virt-viewer-session-spice.c |   47 +++++-
 src/virt-viewer-window.c        |    9 +
 src/virt-viewer-window.h        |    1 +
 9 files changed, 435 insertions(+), 31 deletions(-)

diff --git a/configure.ac b/configure.ac
index e47d60a..b2d7e8f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -92,7 +92,8 @@ AC_ARG_WITH([spice-gtk],
 
 AS_IF([test "x$with_spice_gtk" != "xno"],
       [PKG_CHECK_MODULES(SPICE_GTK,
-                         spice-client-gtk-$SPICE_GTK_API_VERSION >= $SPICE_GTK_REQUIRED,
+                         [spice-client-gtk-$SPICE_GTK_API_VERSION >= $SPICE_GTK_REQUIRED
+                          spice-controller],
                          [have_spice_gtk=yes], [have_spice_gtk=no])],
       [have_spice_gtk=no])
 
diff --git a/src/remote-viewer-main.c b/src/remote-viewer-main.c
index 54670d1..2256528 100644
--- a/src/remote-viewer-main.c
+++ b/src/remote-viewer-main.c
@@ -56,6 +56,7 @@ main(int argc, char **argv)
 	gboolean direct = FALSE;
 	gboolean fullscreen = FALSE;
 	RemoteViewer *viewer = NULL;
+	gboolean controller = FALSE;
 	VirtViewerApp *app;
 	const char *help_msg = N_("Run '" PACKAGE " --help' to see a full list of available command line options");
 	const GOptionEntry options [] = {
@@ -71,6 +72,8 @@ main(int argc, char **argv)
 		  N_("Display debugging information"), NULL },
 		{ "full-screen", 'f', 0, G_OPTION_ARG_NONE, &fullscreen,
 		  N_("Open in full screen mode"), NULL },
+		{ "spice-controller", '\0', 0, G_OPTION_ARG_NONE, &controller,
+		  N_("Open connection using Spice controller communication"), NULL },
 		{ G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_STRING_ARRAY, &args,
 		  NULL, "URI" },
 		{ NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL }
@@ -99,7 +102,8 @@ main(int argc, char **argv)
 
 	g_option_context_free(context);
 
-	if (!args || (g_strv_length(args) != 1)) {
+	if ((!args || (g_strv_length(args) != 1)) &&
+	    !controller) {
 		g_printerr(_("\nUsage: %s [OPTIONS] URI\n\n%s\n\n"), argv[0], help_msg);
 		goto cleanup;
 	}
@@ -111,15 +115,19 @@ main(int argc, char **argv)
 
 	virt_viewer_app_set_debug(debug);
 
-	viewer = remote_viewer_new(args[0], verbose);
+	if (controller) {
+		viewer = remote_viewer_new_with_controller(verbose);
+		g_object_set(viewer, "guest-name", "defined by Spice controller", NULL);
+	} else {
+		viewer = remote_viewer_new(args[0], verbose);
+		g_object_set(viewer, "guest-name", args[0], NULL);
+
+	}
 	if (viewer == NULL)
 		goto cleanup;
 
 	app = VIRT_VIEWER_APP(viewer);
-	g_object_set(app,
-		     "fullscreen", fullscreen,
-		     "guest-name", args[0],
-		     NULL);
+	g_object_set(app, "fullscreen", fullscreen, NULL);
 	virt_viewer_window_set_zoom_level(virt_viewer_app_get_main_window(app), zoom);
 	virt_viewer_app_set_direct(app, direct);
 
diff --git a/src/remote-viewer.c b/src/remote-viewer.c
index d5c9824..388531b 100644
--- a/src/remote-viewer.c
+++ b/src/remote-viewer.c
@@ -27,24 +27,41 @@
 #include <glib/gprintf.h>
 #include <glib/gi18n.h>
 
+#include <spice-controller/spice-controller.h>
+
+#include "virt-viewer-session-spice.h"
 #include "virt-viewer-app.h"
 #include "remote-viewer.h"
 
 struct _RemoteViewerPrivate {
-	int _dummy;
+	SpiceCtrlController *controller;
+	GtkWidget *controller_menu;
 };
 
 G_DEFINE_TYPE (RemoteViewer, remote_viewer, VIRT_VIEWER_TYPE_APP)
 #define GET_PRIVATE(o)							\
         (G_TYPE_INSTANCE_GET_PRIVATE ((o), REMOTE_VIEWER_TYPE, RemoteViewerPrivate))
 
+enum {
+	PROP_0,
+	PROP_CONTROLLER,
+};
+
 static gboolean remote_viewer_start(VirtViewerApp *self);
+static int remote_viewer_activate(VirtViewerApp *self);
+static void remote_viewer_window_added(VirtViewerApp *self, VirtViewerWindow *win);
 
 static void
 remote_viewer_get_property (GObject *object, guint property_id,
-			    GValue *value G_GNUC_UNUSED, GParamSpec *pspec)
+			   GValue *value, GParamSpec *pspec)
 {
+	RemoteViewer *self = REMOTE_VIEWER(object);
+	RemoteViewerPrivate *priv = self->priv;
+
 	switch (property_id) {
+	case PROP_CONTROLLER:
+		g_value_set_object(value, priv->controller);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
 	}
@@ -52,9 +69,16 @@ remote_viewer_get_property (GObject *object, guint property_id,
 
 static void
 remote_viewer_set_property (GObject *object, guint property_id,
-			    const GValue *value G_GNUC_UNUSED, GParamSpec *pspec)
+			   const GValue *value, GParamSpec *pspec)
 {
+	RemoteViewer *self = REMOTE_VIEWER(object);
+	RemoteViewerPrivate *priv = self->priv;
+
 	switch (property_id) {
+	case PROP_CONTROLLER:
+		g_return_if_fail(priv->controller == NULL);
+		priv->controller = g_value_dup_object(value);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
 	}
@@ -63,6 +87,14 @@ remote_viewer_set_property (GObject *object, guint property_id,
 static void
 remote_viewer_dispose (GObject *object)
 {
+	RemoteViewer *self = REMOTE_VIEWER(object);
+	RemoteViewerPrivate *priv = self->priv;
+
+	if (priv->controller) {
+		g_object_unref(priv->controller);
+		priv->controller = NULL;
+	}
+
 	G_OBJECT_CLASS(remote_viewer_parent_class)->dispose (object);
 }
 
@@ -79,6 +111,18 @@ remote_viewer_class_init (RemoteViewerClass *klass)
 	object_class->dispose = remote_viewer_dispose;
 
 	app_class->start = remote_viewer_start;
+	app_class->activate = remote_viewer_activate;
+	app_class->window_added = remote_viewer_window_added;
+
+	g_object_class_install_property(object_class,
+					PROP_CONTROLLER,
+					g_param_spec_object("controller",
+							    "Controller",
+							    "Spice controller",
+							    SPICE_CTRL_TYPE_CONTROLLER,
+							    G_PARAM_READWRITE |
+							    G_PARAM_CONSTRUCT_ONLY |
+							    G_PARAM_STATIC_STRINGS));
 }
 
 static void
@@ -96,36 +140,326 @@ remote_viewer_new(const gchar *uri, gboolean verbose)
 			    NULL);
 }
 
+RemoteViewer *
+remote_viewer_new_with_controller(gboolean verbose)
+{
+	RemoteViewer *self;
+	SpiceCtrlController *ctrl = spice_ctrl_controller_new();
+
+	self =  g_object_new(REMOTE_VIEWER_TYPE,
+			     "controller", ctrl,
+			     "verbose", verbose,
+			     NULL);
+	g_object_unref(ctrl);
+
+	return self;
+}
+
+static void
+spice_ctrl_do_connect(SpiceCtrlController *ctrl G_GNUC_UNUSED,
+		      VirtViewerApp *self)
+{
+	if (virt_viewer_app_initial_connect(self) < 0) {
+		virt_viewer_app_simple_message_dialog(self, _("Failed to initiate connection"));
+	}
+}
+
+static void
+spice_ctrl_show(SpiceCtrlController *ctrl G_GNUC_UNUSED, RemoteViewer *self)
+{
+	virt_viewer_app_show_display(VIRT_VIEWER_APP(self));
+}
+
+static void
+spice_ctrl_hide(SpiceCtrlController *ctrl G_GNUC_UNUSED, RemoteViewer *self)
+{
+	virt_viewer_app_show_status(VIRT_VIEWER_APP(self), _("Display disabled by controller"));
+}
+
+static void
+spice_menuitem_activate_cb(GtkMenuItem *mi, RemoteViewer *self)
+{
+	SpiceCtrlMenuItem *menuitem = g_object_get_data(G_OBJECT(mi), "spice-menuitem");
+
+	g_return_if_fail(menuitem != NULL);
+	if (gtk_menu_item_get_submenu(mi))
+		return;
+
+	spice_ctrl_controller_menu_item_click_msg(self->priv->controller, menuitem->id);
+}
+
+static GtkWidget *
+ctrlmenu_to_gtkmenu (RemoteViewer *self, SpiceCtrlMenu *ctrlmenu)
+{
+	GList *l;
+	GtkWidget *menu = gtk_menu_new();
+	guint n = 0;
+
+	for (l = ctrlmenu->items; l != NULL; l = l->next) {
+		SpiceCtrlMenuItem *menuitem = l->data;
+		GtkWidget *item;
+		char *s;
+		if (menuitem->text == NULL) {
+			g_warn_if_reached();
+			continue;
+		}
+
+		for (s = menuitem->text; *s; s++)
+			if (*s == '&')
+				*s = '_';
+
+		if (g_str_equal(menuitem->text, "-")){
+			item = gtk_separator_menu_item_new();
+		} else {
+			item = gtk_menu_item_new_with_mnemonic(menuitem->text);
+		}
+
+		g_object_set_data_full(G_OBJECT(item), "spice-menuitem",
+					g_object_ref(menuitem), g_object_unref);
+		g_signal_connect(item, "activate", G_CALLBACK(spice_menuitem_activate_cb), self);
+		gtk_menu_attach(GTK_MENU (menu), item, 0, 1, n, n + 1);
+		n += 1;
+
+		if (menuitem->submenu) {
+			gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
+						  ctrlmenu_to_gtkmenu(self, menuitem->submenu));
+		}
+	}
+
+	if (n == 0) {
+		g_object_ref_sink(menu);
+		g_object_unref(menu);
+		menu = NULL;
+	}
+
+	gtk_widget_show_all(menu);
+	return menu;
+}
+
+static void
+spice_menu_set_visible(gpointer key G_GNUC_UNUSED,
+		       gpointer value,
+		       gpointer user_data)
+{
+	gboolean visible = GPOINTER_TO_INT(user_data);
+	GtkWidget *menu = g_object_get_data(value, "spice-menu");
+
+	gtk_widget_set_visible(menu, visible);
+}
+
+static void
+remote_viewer_window_spice_menu_set_visible(RemoteViewer *self,
+					   gboolean visible)
+{
+	GHashTable *windows = virt_viewer_app_get_windows(VIRT_VIEWER_APP(self));
+
+	g_hash_table_foreach(windows, spice_menu_set_visible, GINT_TO_POINTER(visible));
+}
+
+static void
+spice_menu_update(gpointer key G_GNUC_UNUSED,
+		  gpointer value,
+		  gpointer user_data)
+{
+	RemoteViewer *self = REMOTE_VIEWER(user_data);
+	GtkWidget *menuitem = g_object_get_data(value, "spice-menu");
+	SpiceCtrlMenu *menu;
+
+	g_object_get(self->priv->controller, "menu", &menu, NULL);
+	gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), ctrlmenu_to_gtkmenu(self, menu));
+	g_object_unref(menu);
+}
+
+static void
+spice_ctrl_menu_updated(RemoteViewer *self,
+			SpiceCtrlMenu *menu)
+{
+	GHashTable *windows = virt_viewer_app_get_windows(VIRT_VIEWER_APP(self));
+	RemoteViewerPrivate *priv = self->priv;
+	gboolean visible;
+
+	DEBUG_LOG("Spice controller menu updated");
+
+	if (priv->controller_menu != NULL) {
+		g_object_unref (priv->controller_menu);
+		priv->controller_menu = NULL;
+	}
+
+	if (menu && g_list_length(menu->items) > 0) {
+		priv->controller_menu = ctrlmenu_to_gtkmenu(self, menu);
+		g_hash_table_foreach(windows, spice_menu_update, self);
+	}
+
+	visible = priv->controller_menu != NULL;
+
+	remote_viewer_window_spice_menu_set_visible(self, visible);
+}
+
+static SpiceSession *
+remote_viewer_get_spice_session(RemoteViewer *self)
+{
+	VirtViewerSession *vsession = NULL;
+	SpiceSession *session = NULL;
+
+	g_object_get(self, "session", &vsession, NULL);
+	g_return_val_if_fail(vsession != NULL, NULL);
+
+	g_object_get(vsession, "spice-session", &session, NULL);
+
+	g_object_unref(vsession);
+
+	return session;
+}
+
+#ifndef G_VALUE_INIT /* see bug https://bugzilla.gnome.org/show_bug.cgi?id=654793 */
+#define G_VALUE_INIT  { 0, { { 0 } } }
+#endif
+
+static void
+spice_ctrl_notified(SpiceCtrlController *ctrl,
+		    GParamSpec *pspec,
+		    RemoteViewer *self)
+{
+	SpiceSession *session = remote_viewer_get_spice_session(self);
+	GValue value = G_VALUE_INIT;
+	VirtViewerApp *app = VIRT_VIEWER_APP(self);
+
+	g_return_if_fail(session != NULL);
+
+	g_value_init(&value, pspec->value_type);
+	g_object_get_property(G_OBJECT(ctrl), pspec->name, &value);
+
+	if (g_str_equal(pspec->name, "host") ||
+	    g_str_equal(pspec->name, "port") ||
+	    g_str_equal(pspec->name, "password") ||
+	    g_str_equal(pspec->name, "ca-file")) {
+		g_object_set_property(G_OBJECT(session), pspec->name, &value);
+	} else if (g_str_equal(pspec->name, "sport")) {
+		g_object_set_property(G_OBJECT(session), "tls-port", &value);
+	} else if (g_str_equal(pspec->name, "tls-ciphers")) {
+		g_object_set_property(G_OBJECT(session), "ciphers", &value);
+	} else if (g_str_equal(pspec->name, "host-subject")) {
+		g_object_set_property(G_OBJECT(session), "cert-subject", &value);
+	} else if (g_str_equal(pspec->name, "title")) {
+		g_object_set_property(G_OBJECT(app), "title", &value);
+	} else if (g_str_equal(pspec->name, "display-flags")) {
+		guint flags = g_value_get_uint(&value);
+		gboolean fullscreen = flags & CONTROLLER_SET_FULL_SCREEN;
+		gboolean auto_res = flags & CONTROLLER_AUTO_DISPLAY_RES;
+		g_object_set(G_OBJECT(self), "fullscreen", fullscreen, NULL);
+		g_debug("unimplemented resize-guest %d", auto_res);
+		/* g_object_set(G_OBJECT(self), "resize-guest", auto_res, NULL); */
+	} else if (g_str_equal(pspec->name, "menu")) {
+		spice_ctrl_menu_updated(self, g_value_get_object(&value));
+	} else {
+		gchar *content = g_strdup_value_contents(&value);
+
+		g_debug("unimplemented property: %s=%s", pspec->name, content);
+		g_free(content);
+	}
+
+	g_object_unref(session);
+	g_value_unset(&value);
+}
+
+static void
+spice_ctrl_listen_async_cb(GObject *object,
+			   GAsyncResult *res,
+			   gpointer user_data)
+{
+	GError *error = NULL;
+
+	spice_ctrl_controller_listen_finish(SPICE_CTRL_CONTROLLER(object), res, &error);
+
+	if (error != NULL) {
+		virt_viewer_app_simple_message_dialog(VIRT_VIEWER_APP(user_data),
+						      _("Controller connection failed: %s"),
+						      error->message);
+		g_clear_error(&error);
+		exit(1); /* TODO: make start async? */
+	}
+}
+
+static int
+remote_viewer_activate(VirtViewerApp *app)
+{
+	g_return_val_if_fail(REMOTE_VIEWER_IS(app), -1);
+	RemoteViewer *self = REMOTE_VIEWER(app);
+	int ret = -1;
+
+	if (self->priv->controller) {
+		SpiceSession *session = remote_viewer_get_spice_session(self);
+		ret = spice_session_connect(session);
+		g_object_unref(session);
+	} else {
+		ret = VIRT_VIEWER_APP_CLASS(remote_viewer_parent_class)->activate(app);
+	}
+
+	return ret;
+}
+
+static void
+remote_viewer_window_added(VirtViewerApp *self G_GNUC_UNUSED,
+			  VirtViewerWindow *win)
+{
+	GtkMenuShell *shell = GTK_MENU_SHELL(gtk_builder_get_object(virt_viewer_window_get_builder(win), "top-menu"));
+	GtkWidget *spice = gtk_menu_item_new_with_label("Spice");
+
+	gtk_menu_shell_append(shell, spice);
+	g_object_set_data(G_OBJECT(win), "spice-menu", spice);
+}
+
 static gboolean
 remote_viewer_start(VirtViewerApp *app)
 {
-	gchar *guri;
-	gchar *type;
+	g_return_val_if_fail(REMOTE_VIEWER_IS(app), FALSE);
+
+	RemoteViewer *self = REMOTE_VIEWER(app);
+	RemoteViewerPrivate *priv = self->priv;
 	gboolean ret = FALSE;
+	gchar *guri = NULL;
+	gchar *type = NULL;
 
-	g_object_get(app, "guri", &guri, NULL);
-	g_return_val_if_fail(guri != NULL, FALSE);
+	if (priv->controller) {
+		if (virt_viewer_app_create_session(app, "spice") < 0) {
+			virt_viewer_app_simple_message_dialog(app, _("Couldn't create a Spice session"));
+			goto cleanup;
+		}
 
-	DEBUG_LOG("Opening display to %s", guri);
+		g_signal_connect(priv->controller, "notify", G_CALLBACK(spice_ctrl_notified), self);
+		g_signal_connect(priv->controller, "do_connect", G_CALLBACK(spice_ctrl_do_connect), self);
+		g_signal_connect(priv->controller, "show", G_CALLBACK(spice_ctrl_show), self);
+		g_signal_connect(priv->controller, "hide", G_CALLBACK(spice_ctrl_hide), self);
 
-	if (virt_viewer_util_extract_host(guri, &type, NULL, NULL, NULL, NULL) < 0) {
-		virt_viewer_app_simple_message_dialog(app, _("Cannot determine the connection type from URI"));
-		goto cleanup;
-	}
+		spice_ctrl_controller_listen(priv->controller, NULL, spice_ctrl_listen_async_cb, self);
+		virt_viewer_app_show_status(VIRT_VIEWER_APP(self), _("Setting up Spice session..."));
+	} else {
+		g_object_get(app, "guri", &guri, NULL);
+		g_return_val_if_fail(guri != NULL, FALSE);
 
-	if (virt_viewer_app_create_session(app, type) < 0) {
-		virt_viewer_app_simple_message_dialog(app, _("Couldn't create a session for this type: %s"), type);
-		goto cleanup;
-	}
+		DEBUG_LOG("Opening display to %s", guri);
+		g_object_set(app, "title", guri, NULL);
+
+		if (virt_viewer_util_extract_host(guri, &type, NULL, NULL, NULL, NULL) < 0) {
+			virt_viewer_app_simple_message_dialog(app, _("Cannot determine the connection type from URI"));
+			goto cleanup;
+		}
+
+		if (virt_viewer_app_create_session(app, type) < 0) {
+			virt_viewer_app_simple_message_dialog(app, _("Couldn't create a session for this type: %s"), type);
+			goto cleanup;
+		}
+
+		if (virt_viewer_app_initial_connect(app) < 0) {
+			virt_viewer_app_simple_message_dialog(app, _("Failed to initiate connection"));
+			goto cleanup;
+		}
 
-	if (virt_viewer_app_activate(app) < 0) {
-		virt_viewer_app_simple_message_dialog(app, _("Failed to initiate connection"));
-		goto cleanup;
 	}
 
 	ret = VIRT_VIEWER_APP_CLASS(remote_viewer_parent_class)->start(app);
 
- cleanup:
+cleanup:
 	g_free(guri);
 	g_free(type);
 	return ret;
diff --git a/src/remote-viewer.h b/src/remote-viewer.h
index 1ff6d8d..3d02315 100644
--- a/src/remote-viewer.h
+++ b/src/remote-viewer.h
@@ -48,8 +48,8 @@ typedef struct {
 
 GType remote_viewer_get_type (void);
 
-RemoteViewer *
-remote_viewer_new(const gchar *uri, gboolean verbose);
+RemoteViewer* remote_viewer_new(const gchar *uri, gboolean verbose);
+RemoteViewer* remote_viewer_new_with_controller(gboolean verbose);
 
 G_END_DECLS
 
diff --git a/src/virt-viewer-app.c b/src/virt-viewer-app.c
index 97b53c2..352f206 100644
--- a/src/virt-viewer-app.c
+++ b/src/virt-viewer-app.c
@@ -1528,6 +1528,13 @@ virt_viewer_app_show_display(VirtViewerApp *self)
 	g_hash_table_foreach(self->priv->windows, show_display_cb, self);
 }
 
+GHashTable*
+virt_viewer_app_get_windows(VirtViewerApp *self)
+{
+	g_return_val_if_fail(VIRT_VIEWER_IS_APP(self), NULL);
+	return self->priv->windows;
+}
+
 /*
  * Local variables:
  *  c-indent-level: 8
diff --git a/src/virt-viewer-app.h b/src/virt-viewer-app.h
index 7c3f0a7..320e75c 100644
--- a/src/virt-viewer-app.h
+++ b/src/virt-viewer-app.h
@@ -86,6 +86,7 @@ void virt_viewer_app_set_connect_info(VirtViewerApp *self,
 gboolean virt_viewer_app_window_set_visible(VirtViewerApp *self, VirtViewerWindow *window, gboolean visible);
 void virt_viewer_app_show_status(VirtViewerApp *self, const gchar *fmt, ...);
 void virt_viewer_app_show_display(VirtViewerApp *self);
+GHashTable* virt_viewer_app_get_windows(VirtViewerApp *self);
 
 G_END_DECLS
 
diff --git a/src/virt-viewer-session-spice.c b/src/virt-viewer-session-spice.c
index 066f922..f89d042 100644
--- a/src/virt-viewer-session-spice.c
+++ b/src/virt-viewer-session-spice.c
@@ -41,6 +41,12 @@ struct _VirtViewerSessionSpicePrivate {
 
 #define VIRT_VIEWER_SESSION_SPICE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), VIRT_VIEWER_TYPE_SESSION_SPICE, VirtViewerSessionSpicePrivate))
 
+enum {
+	PROP_0,
+	PROP_SPICE_SESSION,
+};
+
+
 static void virt_viewer_session_spice_close(VirtViewerSession *session);
 static gboolean virt_viewer_session_spice_open_fd(VirtViewerSession *session, int fd);
 static gboolean virt_viewer_session_spice_open_host(VirtViewerSession *session, char *host, char *port);
@@ -55,7 +61,33 @@ static void virt_viewer_session_spice_channel_destroy(SpiceSession *s,
 
 
 static void
-virt_viewer_session_spice_finalize(GObject *obj)
+virt_viewer_session_spice_get_property(GObject *object, guint property_id,
+				       GValue *value, GParamSpec *pspec)
+{
+	VirtViewerSessionSpice *self = VIRT_VIEWER_SESSION_SPICE(object);
+	VirtViewerSessionSpicePrivate *priv = self->priv;
+
+	switch (property_id) {
+	case PROP_SPICE_SESSION:
+		g_value_set_object(value, priv->session);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+	}
+}
+
+static void
+virt_viewer_session_spice_set_property(GObject *object, guint property_id,
+				       const GValue *value G_GNUC_UNUSED, GParamSpec *pspec)
+{
+	switch (property_id) {
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+	}
+}
+
+static void
+virt_viewer_session_spice_dispose(GObject *obj)
 {
 	VirtViewerSessionSpice *spice = VIRT_VIEWER_SESSION_SPICE(obj);
 
@@ -76,7 +108,9 @@ virt_viewer_session_spice_class_init(VirtViewerSessionSpiceClass *klass)
 	VirtViewerSessionClass *dclass = VIRT_VIEWER_SESSION_CLASS(klass);
 	GObjectClass *oclass = G_OBJECT_CLASS(klass);
 
-	oclass->finalize = virt_viewer_session_spice_finalize;
+	oclass->get_property = virt_viewer_session_spice_get_property;
+	oclass->set_property = virt_viewer_session_spice_set_property;
+	oclass->dispose = virt_viewer_session_spice_dispose;
 
 	dclass->close = virt_viewer_session_spice_close;
 	dclass->open_fd = virt_viewer_session_spice_open_fd;
@@ -85,6 +119,15 @@ virt_viewer_session_spice_class_init(VirtViewerSessionSpiceClass *klass)
 	dclass->channel_open_fd = virt_viewer_session_spice_channel_open_fd;
 
 	g_type_class_add_private(oclass, sizeof(VirtViewerSessionSpicePrivate));
+
+	g_object_class_install_property(oclass,
+					PROP_SPICE_SESSION,
+					g_param_spec_object("spice-session",
+							    "Spice session",
+							    "Spice session",
+							    SPICE_TYPE_SESSION,
+							    G_PARAM_READABLE |
+							    G_PARAM_STATIC_STRINGS));
 }
 
 static void
diff --git a/src/virt-viewer-window.c b/src/virt-viewer-window.c
index 324e37f..d80b456 100644
--- a/src/virt-viewer-window.c
+++ b/src/virt-viewer-window.c
@@ -912,9 +912,18 @@ GtkMenuItem*
 virt_viewer_window_get_menu_displays(VirtViewerWindow *self)
 {
 	g_return_val_if_fail(VIRT_VIEWER_IS_WINDOW(self), NULL);
+
 	return GTK_MENU_ITEM(gtk_builder_get_object(self->priv->builder, "menu-displays"));
 }
 
+GtkBuilder*
+virt_viewer_window_get_builder(VirtViewerWindow *self)
+{
+	g_return_val_if_fail(VIRT_VIEWER_IS_WINDOW(self), NULL);
+
+	return self->priv->builder;
+}
+
 /*
  * Local variables:
  *  c-indent-level: 8
diff --git a/src/virt-viewer-window.h b/src/virt-viewer-window.h
index 9baab76..cf66f5e 100644
--- a/src/virt-viewer-window.h
+++ b/src/virt-viewer-window.h
@@ -69,6 +69,7 @@ gint virt_viewer_window_get_zoom_level(VirtViewerWindow *self);
 void virt_viewer_window_leave_fullscreen(VirtViewerWindow *self);
 void virt_viewer_window_enter_fullscreen(VirtViewerWindow *self, gboolean move, gint x, gint y);
 GtkMenuItem *virt_viewer_window_get_menu_displays(VirtViewerWindow *self);
+GtkBuilder* virt_viewer_window_get_builder(VirtViewerWindow *window);
 
 G_END_DECLS
 
-- 
1.7.7.3




More information about the virt-tools-list mailing list