Add support for SPICE. master 0.2
authorRichard W.M. Jones <rjones@redhat.com>
Fri, 8 Jul 2011 11:15:05 +0000 (12:15 +0100)
committerRichard W.M. Jones <rjones@redhat.com>
Mon, 11 Jul 2011 13:24:43 +0000 (14:24 +0100)
.gitignore
Makefile.am
click.h
commands.c
configure.ac
main.c
po/virt-click.pot [new file with mode: 0644]
spice.c [new file with mode: 0644]
virt-click.pod
vnc.c

index 1cf1e24..17eed6d 100644 (file)
@@ -63,6 +63,7 @@ Makefile
 /po/quot.sed
 /po/remove-potcdate.sed
 /po/remove-potcdate.sin
+/po/stamp-po
 /stamp-h1
 /virt-click
 /virt-click.1
index 3ee0afe..3f7bf76 100644 (file)
@@ -31,11 +31,17 @@ virt_click_SOURCES = \
        click.h \
        commands.c \
        main.c \
+       spice.c \
+       spice.h \
        vnc.c \
        vnc-auth.c
 
-virt_click_CFLAGS = $(GVNC_CFLAGS) $(SPICE_GTK_CFLAGS)
-virt_click_LDADD = $(GVNC_LIBS) $(SPICE_GTK_LIBS)
+virt_click_CFLAGS = \
+       $(GVNC_CFLAGS) \
+       $(SPICE_CLIENT_GLIB_CFLAGS)
+virt_click_LDADD = \
+       $(GVNC_LIBS) \
+       $(SPICE_CLIENT_GLIB_LIBS)
 
 man_MANS = virt-click.1
 
diff --git a/click.h b/click.h
index 64daefc..0f81203 100644 (file)
--- a/click.h
+++ b/click.h
@@ -19,8 +19,9 @@
 #ifndef click_h_
 #define click_h_
 
-#include <vncconnection.h>
 #include <glib-object.h>
+#include <vncconnection.h>
+#include <spice-client.h>
 
 enum command_qual {
   CMD_CLICK
@@ -31,7 +32,7 @@ typedef struct {
   union {
     struct {
       int x, y;                 /* x, y location */
-      unsigned mask;            /* button mask */
+      int b;                    /* button number */
     } click;
   };
 } command_t;
@@ -40,12 +41,13 @@ struct self_t;
 
 typedef struct {
   void (*shutdown) (struct self_t *self);
-  int (*click) (struct self_t *self, int x, int y, unsigned mask);
+  int (*click) (struct self_t *self, int x, int y, int b);
 } callbacks_t;
 
 typedef struct self_t {
   GMainLoop *loop;
   int ret;
+  gboolean connected;
 
   /* Callbacks. */
   callbacks_t *callbacks;
@@ -54,10 +56,12 @@ typedef struct self_t {
   command_t command;
 
   /* Private data for VNC connections: */
-  gchar *host;
-  int port;
   VncConnection *conn;
-  gboolean connected;
+
+  /* Private data for SPICE connections. */
+  SpiceSession *session;
+  SpiceChannel *display_channel;
+  SpiceChannel *inputs_channel;
 } self_t;
 
 extern gboolean verbose;
@@ -66,8 +70,15 @@ extern gboolean verbose;
 extern int vc_parse_command (self_t *self, gchar **args, guint nr_args);
 extern void vc_issue_command (self_t *self);
 
+/* spice.c */
+extern GOptionGroup *vc_spice_cmdline_get_option_group (void);
+extern gboolean vc_spice_is_selected (void);
+extern void vc_spice_setup (self_t *self);
+
 /* vnc.c */
-extern void vc_vnc_setup (self_t *click, gchar *vnc);
+extern GOptionGroup *vc_vnc_cmdline_get_option_group (void);
+extern gboolean vc_vnc_is_selected (void);
+extern void vc_vnc_setup (self_t *self);
 
 /* vnc-auth.c */
 extern void vc_vnc_auth_credential (VncConnection *conn, GValueArray *credList, gpointer opaque);
index 2564bfd..1c981ad 100644 (file)
@@ -27,6 +27,8 @@
 
 #include "click.h"
 
+static gboolean click_release (gpointer opaque);
+
 static int
 parse_click (self_t *self, gchar **args, guint nr_args)
 {
@@ -59,7 +61,7 @@ parse_click (self_t *self, gchar **args, guint nr_args)
       return -1;
   }
 
-  self->command.click.mask = 1 << (b-1);
+  self->command.click.b = b;
   self->command.cmd = CMD_CLICK;
 
   return 0;
@@ -74,6 +76,15 @@ vc_parse_command (self_t *self, gchar **args, guint nr_args)
   return -1;
 }
 
+#define NOT_SUPPORTED(cmd) \
+  do { \
+    fprintf (stderr, "virt-click: %s: "                                 \
+             "command is not implemented for this connection type\n", cmd); \
+    self->ret = EXIT_FAILURE;                                           \
+    self->callbacks->shutdown (self);                                   \
+    return;                                                             \
+  } while (0)
+
 void
 vc_issue_command (self_t *self)
 {
@@ -82,21 +93,49 @@ vc_issue_command (self_t *self)
   switch (self->command.cmd) {
   case CMD_CLICK:
     if (verbose)
-      fprintf (stderr, "click x=%d y=%d mask=0x%x\n",
+      fprintf (stderr, "click x=%d y=%d b=%d\n",
                self->command.click.x, self->command.click.y,
-               self->command.click.mask);
+               self->command.click.b);
+
+    if (!self->callbacks->click)
+      NOT_SUPPORTED ("click");
 
     r = self->callbacks->click (self,
-                                self->command.click.x,
-                                self->command.click.y,
-                                self->command.click.mask);
+                                self->command.click.x, self->command.click.y,
+                                self->command.click.b);
+    if (r == -1)
+      goto out;
+
+    g_timeout_add (100, click_release, self);
+
     break;
   }
 
+ out:
   if (r == -1) {
     fprintf (stderr, "virt-click: error sending command to remote server\n");
     self->ret = EXIT_FAILURE;
+    self->callbacks->shutdown (self);
   }
+}
+
+/* To perform a button press in VNC we have to send the button press
+ * event, wait a short period, then send a button release event (ie.
+ * no buttons pressed).
+ */
+static gboolean
+click_release (gpointer opaque)
+{
+  self_t *self = opaque;
+
+  if (verbose)
+    fprintf (stderr, "click release x=%d y=%d\n",
+             self->command.click.x, self->command.click.y);
+
+  self->callbacks->click (self,
+                          self->command.click.x, self->command.click.y,
+                          0);
+  self->callbacks->shutdown (self);
 
-  /* XXX Shutdown connection. */
+  return FALSE;
 }
index 643066c..f1ff893 100644 (file)
@@ -15,7 +15,7 @@ dnl You should have received a copy of the GNU General Public License
 dnl along with this program; if not, write to the Free Software
 dnl Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
-AC_INIT([virt-click],[0.1])
+AC_INIT([virt-click],[0.2])
 AM_INIT_AUTOMAKE([foreign])
 
 AC_CONFIG_MACRO_DIR([m4])
diff --git a/main.c b/main.c
index 1f98c77..04c1290 100644 (file)
--- a/main.c
+++ b/main.c
@@ -49,7 +49,6 @@ main (int argc, char **argv)
   GOptionContext *context;
   GError *error = NULL;
   gboolean version = 0;
-  gchar *vnc = NULL;
   gchar **args = NULL;
   guint nr_args;
   const GOptionEntry options [] = {
@@ -57,8 +56,6 @@ main (int argc, char **argv)
       &verbose, "Enables verbose output", NULL },
     { "version", 'V', 0,  G_OPTION_ARG_NONE,
       &version, "Display version and exit", NULL },
-    { "vnc", 0, 0,  G_OPTION_ARG_STRING,
-      &vnc, "Connect to VNC server directly", NULL },
     { G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_STRING_ARRAY, &args,
       NULL, "(click|...) [args...]" },
     { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, 0 }
@@ -80,6 +77,8 @@ main (int argc, char **argv)
      "    click x y [b]                   Mouse click at (x, y), with button b"
      );
   g_option_context_add_main_entries (context, options, NULL);
+  g_option_context_add_group (context, vc_vnc_cmdline_get_option_group ());
+  g_option_context_add_group (context, vc_spice_cmdline_get_option_group ());
   g_option_context_parse (context, &argc, &argv, &error);
   if (error) {
     short_usage (argv[0], error->message);
@@ -108,8 +107,11 @@ main (int argc, char **argv)
   }
 
   /* Create VNC/SPICE/... object. */
-  if (vnc)
-    vc_vnc_setup (self, vnc);
+  if (vc_vnc_is_selected ())
+    vc_vnc_setup (self);
+  else if (vc_spice_is_selected ()) {
+    vc_spice_setup (self);
+  }
   else {
     short_usage (argv[0], "no --vnc or --spice option");
     exit (EXIT_FAILURE);
diff --git a/po/virt-click.pot b/po/virt-click.pot
new file mode 100644 (file)
index 0000000..9bc95ac
--- /dev/null
@@ -0,0 +1,26 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR Red Hat Inc.
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: virt-click 0.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2011-07-08 13:12+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: vnc.c:76
+msgid "VNC Options:"
+msgstr ""
+
+#: vnc.c:77
+msgid "Show VNC Options"
+msgstr ""
diff --git a/spice.c b/spice.c
new file mode 100644 (file)
index 0000000..dff4a9b
--- /dev/null
+++ b/spice.c
@@ -0,0 +1,234 @@
+/*
+   Copyright (C) 2010 Red Hat, Inc.
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <glib/gi18n.h>
+
+#include <spice-client.h>
+#include <spice-util.h>
+#include <channel-display.h>
+
+#include "click.h"
+
+static void shutdown (self_t *self);
+static int click (struct self_t *self, int x, int y, int b);
+
+static char *host;
+static char *port;
+static char *tls_port;
+static char *password;
+static char *uri;
+static char *ca_file;
+static char *host_subject;
+static char *smartcard_db;
+static char *smartcard_certificates;
+static gboolean smartcard = FALSE;
+
+static GOptionEntry spice_entries[] = {
+    {
+        .long_name        = "spice-uri",
+        .arg              = G_OPTION_ARG_STRING,
+        .arg_data         = &uri,
+        .description      = N_("Spice server uri"),
+        .arg_description  = N_("<uri>"),
+    },{
+        .long_name        = "spice-host",
+        .arg              = G_OPTION_ARG_STRING,
+        .arg_data         = &host,
+        .description      = N_("Spice server address"),
+        .arg_description  = N_("<host>"),
+    },{
+        .long_name        = "spice-port",
+        .arg              = G_OPTION_ARG_STRING,
+        .arg_data         = &port,
+        .description      = N_("Spice server port"),
+        .arg_description  = N_("<port>"),
+    },{
+        .long_name        = "spice-secure-port",
+        .arg              = G_OPTION_ARG_STRING,
+        .arg_data         = &tls_port,
+        .description      = N_("Spice server secure port"),
+        .arg_description  = N_("<port>"),
+    },{
+        .long_name        = "spice-ca-file",
+        .arg              = G_OPTION_ARG_FILENAME,
+        .arg_data         = &ca_file,
+        .description      = N_("Truststore file for secure connections"),
+        .arg_description  = N_("<file>"),
+    },{
+        .long_name        = "spice-password",
+        .arg              = G_OPTION_ARG_STRING,
+        .arg_data         = &password,
+        .description      = N_("Server password"),
+        .arg_description  = N_("<password>"),
+    },{
+        .long_name        = "spice-host-subject",
+        .arg              = G_OPTION_ARG_STRING,
+        .arg_data         = &host_subject,
+        .description      = N_("Subject of the host certificate (field=value pairs separated by commas)"),
+        .arg_description  = N_("<host-subject>"),
+    },{
+        /* end of list */
+    }
+};
+
+static GOptionGroup *spice_group;
+
+GOptionGroup *
+vc_spice_cmdline_get_option_group(void)
+{
+    if (spice_group == NULL) {
+        spice_group = g_option_group_new("spice",
+                                         _("Spice Options:"),
+                                         _("Show spice Options"),
+                                         NULL, NULL);
+        g_option_group_add_entries(spice_group, spice_entries);
+    }
+    return spice_group;
+}
+
+static void
+primary_create (SpiceChannel *channel, gint format,
+                gint width, gint height, gint stride,
+                gint shmid, gpointer imgdata, gpointer opaque)
+{
+  self_t *self = opaque;
+
+  if (verbose)
+    fprintf (stderr, "Connected to spice server (primary display channel)\n");
+
+  /* Remember that we managed to connect. */
+  self->connected = TRUE;
+
+  vc_issue_command (self);
+}
+
+static void
+channel_new (SpiceSession *s, SpiceChannel *channel, gpointer opaque)
+{
+  self_t *self = opaque;
+
+  if (SPICE_IS_INPUTS_CHANNEL (channel))
+    self->inputs_channel = channel;
+  if (SPICE_IS_DISPLAY_CHANNEL (channel))
+    self->display_channel = channel;
+
+  if (SPICE_IS_DISPLAY_CHANNEL (channel))
+    g_signal_connect (channel, "display-primary-create",
+                      G_CALLBACK (primary_create), self);
+  spice_channel_connect (channel);
+}
+
+static callbacks_t callbacks = {
+  .click = click,
+  .shutdown = shutdown,
+};
+
+void
+vc_spice_setup (self_t *self)
+{
+  if (verbose)
+    spice_util_set_debug (TRUE);
+
+  self->callbacks = &callbacks;
+
+  self->session = spice_session_new ();
+  g_signal_connect (self->session, "channel-new",
+                    G_CALLBACK(channel_new), self);
+
+  if (ca_file == NULL) {
+    const char *homedir = g_getenv("HOME");
+    if (!homedir)
+      homedir = g_get_home_dir();
+    ca_file = g_strdup_printf("%s/.spicec/spice_truststore.pem", homedir);
+  }
+
+  if (uri)
+    g_object_set(self->session, "uri", uri, NULL);
+  if (host)
+    g_object_set(self->session, "host", host, NULL);
+  if (port)
+    g_object_set(self->session, "port", port, NULL);
+  if (tls_port)
+    g_object_set(self->session, "tls-port", tls_port, NULL);
+  if (password)
+    g_object_set(self->session, "password", password, NULL);
+  if (ca_file)
+    g_object_set(self->session, "ca-file", ca_file, NULL);
+  if (host_subject)
+    g_object_set(self->session, "cert-subject", host_subject, NULL);
+
+  if (!spice_session_connect (self->session)) {
+    fprintf (stderr, _("spice: session connection failed\n"));
+    exit (EXIT_FAILURE);
+  }
+}
+
+gboolean
+vc_spice_is_selected (void)
+{
+  return uri != NULL || host != NULL;
+}
+
+static int
+get_channel_id (SpiceChannel *channel)
+{
+  int id;
+  g_object_get (channel, "channel-id", &id, NULL);
+  return id;
+}
+
+
+static void
+shutdown (self_t *self)
+{
+  spice_session_disconnect (self->session);
+  g_object_unref (self->session);
+}
+
+static int
+click (struct self_t *self, int x, int y, int b)
+{
+  int id = get_channel_id (self->display_channel);
+
+  spice_inputs_position (SPICE_INPUTS_CHANNEL (self->inputs_channel),
+                         x, y, id, 0);
+
+  switch (b) {
+  case 0: b = 0; break;
+  case 1: b = SPICE_MOUSE_BUTTON_LEFT; break;
+  case 2: b = SPICE_MOUSE_BUTTON_MIDDLE; break;
+  case 3: b = SPICE_MOUSE_BUTTON_RIGHT; break;
+  case 4: b = SPICE_MOUSE_BUTTON_UP; break;
+  case 5: b = SPICE_MOUSE_BUTTON_DOWN; break;
+  default:
+    fprintf (stderr, "spice: button %d is not supported\n", b);
+    return -1;
+  }
+
+  if (b)
+    spice_inputs_button_press (SPICE_INPUTS_CHANNEL (self->inputs_channel),
+                               b, 0);
+
+  return 0;
+}
index 05655d0..ff6deea 100644 (file)
@@ -8,6 +8,8 @@ virt-click - Send mouse click and key press events to virtual machines
 
  virt-click --vnc hostname[:display] command [args...]
 
+ virt-click --spice-host hostname command [args...]
+
 =head1 DESCRIPTION
 
 Virt-click can be used to send simulated mouse clicks and key presses
@@ -25,6 +27,10 @@ complete list of commands, see L</COMMANDS> below.
 Connect directly to VNC port 5901 on the local machine.  Simulate a
 right mouse click at screen coordinates C<(200,200)>.
 
+ virt-click --spice-host localhost --spice-port 5901 click 200 200 right
+
+Same as above, but for SPICE.
+
 =head1 OPTIONS
 
 =over 4
@@ -33,6 +39,23 @@ right mouse click at screen coordinates C<(200,200)>.
 
 Display brief help.
 
+=item B<--spice-uri uri>
+
+=item B<--spice-host host>
+
+=item B<--spice-port port>
+
+=item B<--spice-secure-port port>
+
+=item B<--spice-ca-file file>
+
+=item B<--spice-password password>
+
+=item B<--spice-host-subject host-subj>
+
+Connect directly to a SPICE server.  Either I<--spice-uri> or
+I<--spice-host> must be specified.  The others are optional.
+
 =item B<-v>
 
 Enable verbose messages for debugging.
diff --git a/vnc.c b/vnc.c
index 264b5d3..abfa0fa 100644 (file)
--- a/vnc.c
+++ b/vnc.c
@@ -51,7 +51,7 @@
 
 static void initialized (VncConnection *conn, gpointer opaque);
 static void disconnected (VncConnection *conn, gpointer opaque);
-static int click (struct self_t *self, int x, int y, unsigned mask);
+static int click (struct self_t *self, int x, int y, int b);
 static void shutdown (self_t *self);
 
 static callbacks_t callbacks = {
@@ -59,28 +59,60 @@ static callbacks_t callbacks = {
   .shutdown = shutdown,
 };
 
+static gchar *vnc;
+static gchar *host;
+static int port;
+
+static GOptionEntry vnc_entries[] = {
+  { "vnc", 0, 0,  G_OPTION_ARG_STRING,
+    &vnc, "Connect to VNC server directly", NULL },
+  { NULL }
+};
+static GOptionGroup *vnc_group;
+
+GOptionGroup *
+vc_vnc_cmdline_get_option_group (void)
+{
+  if (vnc_group == NULL) {
+    vnc_group = g_option_group_new ("vnc",
+                                    _("VNC Options:"),
+                                    _("Show VNC Options"),
+                                    NULL, NULL);
+    g_option_group_add_entries (vnc_group, vnc_entries);
+  }
+  return vnc_group;
+}
+
+gboolean
+vc_vnc_is_selected (void)
+{
+  return vnc != NULL;
+}
+
 void
-vc_vnc_setup (self_t *self, gchar *vnc)
+vc_vnc_setup (self_t *self)
 {
   gchar *display;
-  gchar *port;
+  gchar *port_str;
+
+  self->callbacks = &callbacks;
 
   if (verbose)
     vnc_util_set_debug (TRUE);
 
   if (vnc[0] == ':') {
-    self->host = g_strdup ("localhost");
+    host = g_strdup ("localhost");
     display = vnc;
   } else {
-    self->host = g_strdup (vnc);
-    display = strchr (self->host, ':');
+    host = g_strdup (vnc);
+    display = strchr (host, ':');
   }
   if (display) {
     *display = 0;
     display++;
-    self->port = 5900 + atoi (display);
+    port = 5900 + atoi (display);
   } else
-    self->port = 5900;
+    port = 5900;
 
   self->conn = vnc_connection_new ();
 
@@ -95,10 +127,9 @@ vc_vnc_setup (self_t *self, gchar *vnc)
   g_signal_connect (self->conn, "vnc-auth-credential",
                     G_CALLBACK(vc_vnc_auth_credential), self);
 
-  port = g_strdup_printf ("%d", self->port);
-  vnc_connection_open_host (self->conn, self->host, port);
-
-  self->callbacks = &callbacks;
+  port_str = g_strdup_printf ("%d", port);
+  vnc_connection_open_host (self->conn, host, port_str);
+  g_free (port_str);
 }
 
 static void
@@ -116,7 +147,7 @@ initialized (VncConnection *conn,
   int n_encodings;
 
   if (verbose)
-    fprintf (stderr, "Connected to %s:%d\n", self->host, self->port - 5900);
+    fprintf (stderr, "Connected to %s:%d\n", host, port - 5900);
 
   /* Remember that we managed to connect. */
   self->connected = TRUE;
@@ -148,10 +179,10 @@ disconnected (VncConnection *conn G_GNUC_UNUSED,
     self->ret = EXIT_SUCCESS;
     if (verbose)
       fprintf (stderr, "Disconnected from %s:%d\n",
-               self->host, self->port - 5900);
+               host, port - 5900);
   } else {
     fprintf (stderr, "vnc: unable to connect to %s:%d\n",
-             self->host, self->port - 5900);
+             host, port - 5900);
     self->ret = EXIT_FAILURE;
   }
 
@@ -163,33 +194,16 @@ shutdown (self_t *self)
 {
   vnc_connection_shutdown (self->conn);
   g_object_unref (self->conn);
-  g_free(self->host);
+  g_free (host);
 }
 
-/* To perform a button press in VNC we have to send the button press
- * event, wait a short period, then send a button release event (ie.
- * no buttons pressed).
- */
-static gboolean click_release (gpointer opaque);
-
 static int
-click (struct self_t *self, int x, int y, unsigned mask)
+click (struct self_t *self, int x, int y, int b)
 {
+  unsigned mask = b > 0 ? 1 << (b-1) : 0;
+
   if (!vnc_connection_pointer_event (self->conn, mask, x, y))
     return -1;
 
-  g_timeout_add (100, click_release, self);
-}
-
-static gboolean
-click_release (gpointer opaque)
-{
-  self_t *self = opaque;
-
-  vnc_connection_pointer_event (self->conn, 0,
-                                self->command.click.x, self->command.click.y);
-
-  vnc_connection_shutdown (self->conn);
-
-  return FALSE;
+  return 0;
 }