From 14308c0ee642670e61c74339ebbf1502bc0302bc Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Fri, 8 Jul 2011 12:15:05 +0100 Subject: [PATCH] Add support for SPICE. --- .gitignore | 1 + Makefile.am | 10 ++- click.h | 25 ++++-- commands.c | 53 +++++++++++-- configure.ac | 2 +- main.c | 12 +-- po/virt-click.pot | 26 ++++++ spice.c | 234 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ virt-click.pod | 23 ++++++ vnc.c | 88 +++++++++++--------- 10 files changed, 415 insertions(+), 59 deletions(-) create mode 100644 po/virt-click.pot create mode 100644 spice.c diff --git a/.gitignore b/.gitignore index 1cf1e24..17eed6d 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/Makefile.am b/Makefile.am index 3ee0afe..3f7bf76 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 --- a/click.h +++ b/click.h @@ -19,8 +19,9 @@ #ifndef click_h_ #define click_h_ -#include #include +#include +#include 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); diff --git a/commands.c b/commands.c index 2564bfd..1c981ad 100644 --- a/commands.c +++ b/commands.c @@ -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; } diff --git a/configure.ac b/configure.ac index 643066c..f1ff893 100644 --- a/configure.ac +++ b/configure.ac @@ -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 --- 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 index 0000000..9bc95ac --- /dev/null +++ b/po/virt-click.pot @@ -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 , 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 \n" +"Language-Team: LANGUAGE \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 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 . +*/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include + +#include +#include +#include + +#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_(""), + },{ + .long_name = "spice-host", + .arg = G_OPTION_ARG_STRING, + .arg_data = &host, + .description = N_("Spice server address"), + .arg_description = N_(""), + },{ + .long_name = "spice-port", + .arg = G_OPTION_ARG_STRING, + .arg_data = &port, + .description = N_("Spice server port"), + .arg_description = N_(""), + },{ + .long_name = "spice-secure-port", + .arg = G_OPTION_ARG_STRING, + .arg_data = &tls_port, + .description = N_("Spice server secure port"), + .arg_description = N_(""), + },{ + .long_name = "spice-ca-file", + .arg = G_OPTION_ARG_FILENAME, + .arg_data = &ca_file, + .description = N_("Truststore file for secure connections"), + .arg_description = N_(""), + },{ + .long_name = "spice-password", + .arg = G_OPTION_ARG_STRING, + .arg_data = &password, + .description = N_("Server password"), + .arg_description = N_(""), + },{ + .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_(""), + },{ + /* 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; +} diff --git a/virt-click.pod b/virt-click.pod index 05655d0..ff6deea 100644 --- a/virt-click.pod +++ b/virt-click.pod @@ -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 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 --- 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; } -- 1.8.3.1