[virt-tools-list] [PATCH virt-viewer 08/17] Add spice controller support in remote-viewer
Marc-André Lureau
marcandre.lureau at gmail.com
Fri Jan 27 13:51:27 UTC 2012
From: Marc-André Lureau <marcandre.lureau at redhat.com>
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 +-
4 files changed, 372 insertions(+), 29 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
--
1.7.7.6
More information about the virt-tools-list
mailing list