/po/quot.sed
/po/remove-potcdate.sed
/po/remove-potcdate.sin
+/po/stamp-po
/stamp-h1
/virt-click
/virt-click.1
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
#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
union {
struct {
int x, y; /* x, y location */
- unsigned mask; /* button mask */
+ int b; /* button number */
} click;
};
} command_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;
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;
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);
#include "click.h"
+static gboolean click_release (gpointer opaque);
+
static int
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;
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)
{
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;
}
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])
GOptionContext *context;
GError *error = NULL;
gboolean version = 0;
- gchar *vnc = NULL;
gchar **args = NULL;
guint nr_args;
const GOptionEntry options [] = {
&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 }
" 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);
}
/* 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);
--- /dev/null
+# 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 ""
--- /dev/null
+/*
+ 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;
+}
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
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
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.
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 = {
.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 ();
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
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;
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;
}
{
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;
}