[Spice-commits] configure.ac Makefile.am po/POTFILES.in src/Makefile.am src/spice-cmdline.c src/spice-cmdline.h src/spicy.c src/spicy-connect.c src/spicy-connect.h src/spicy-screenshot.c src/spicy-stats.c tools/Makefile.am tools/spice-cmdline.c tools/spice-cmdline.h tools/spicy.c tools/spicy-connect.c tools/spicy-connect.h tools/spicy-screenshot.c tools/spicy-stats.c

Victor Toso de Carvalho victortoso at kemper.freedesktop.org
Mon Jan 23 08:34:15 UTC 2017


 Makefile.am              |    2 
 configure.ac             |    1 
 po/POTFILES.in           |    2 
 src/Makefile.am          |   53 -
 src/spice-cmdline.c      |   98 --
 src/spice-cmdline.h      |   29 
 src/spicy-connect.c      |  248 ------
 src/spicy-connect.h      |   26 
 src/spicy-screenshot.c   |  194 ----
 src/spicy-stats.c        |  136 ---
 src/spicy.c              | 1938 -----------------------------------------------
 tools/Makefile.am        |   69 +
 tools/spice-cmdline.c    |   98 ++
 tools/spice-cmdline.h    |   29 
 tools/spicy-connect.c    |  248 ++++++
 tools/spicy-connect.h    |   26 
 tools/spicy-screenshot.c |  194 ++++
 tools/spicy-stats.c      |  136 +++
 tools/spicy.c            | 1938 +++++++++++++++++++++++++++++++++++++++++++++++
 19 files changed, 2741 insertions(+), 2724 deletions(-)

New commits:
commit 47e37e7c9db5f70a0ade527fe6045ba742cec8fa
Author: Victor Toso <me at victortoso.com>
Date:   Tue Jan 10 16:36:48 2017 +0100

    Move spicy tools to its own folder
    
    So we can have the tools and the libraries in different folders.
    
    In the src/Makefile.am I've only removed the lines related to the
    tools but not all lines were copied into tools/Makefile.am as we
    don't really need them. Other lines were adjusted to have the paths
    correctly;
    
    Signed-off-by: Victor Toso <victortoso at redhat.com>
    Acked-by: Christophe Fergeau <cfergeau at redhat.com>

diff --git a/Makefile.am b/Makefile.am
index 47cf840..31d4707 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,7 +1,7 @@
 ACLOCAL_AMFLAGS = -I m4
 NULL =
 
-SUBDIRS = spice-common src man po doc data
+SUBDIRS = spice-common src man po doc data tools
 
 if BUILD_TESTS
 SUBDIRS += tests
diff --git a/configure.ac b/configure.ac
index f3e7f8d..4fd0bd7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -590,6 +590,7 @@ po/Makefile.in
 src/Makefile
 src/spice-version.h
 src/controller/Makefile
+tools/Makefile
 doc/Makefile
 doc/reference/Makefile
 man/Makefile
diff --git a/po/POTFILES.in b/po/POTFILES.in
index db42281..d1033f9 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -2,8 +2,8 @@ src/channel-main.c
 src/channel-usbredir.c
 src/desktop-integration.c
 src/spice-channel.c
-src/spice-cmdline.c
 src/spice-option.c
 src/usb-device-manager.c
 src/usb-device-widget.c
 src/usbutil.c
+tools/spice-cmdline.c
diff --git a/src/Makefile.am b/src/Makefile.am
index e43cee0..b991a5f 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -45,10 +45,6 @@ EXTRA_DIST =					\
 
 DISTCLEANFILES = spice-version.h
 
-bin_PROGRAMS = spicy-stats spicy-screenshot
-if WITH_GTK
-bin_PROGRAMS += spicy
-endif
 if WITH_POLKIT
 acldir = $(ACL_HELPER_DIR)
 acl_PROGRAMS = spice-client-glib-usb-acl-helper
@@ -383,31 +379,6 @@ endif
 libspice_client_glib_2_0_la_LIBADD += -lws2_32 -lgdi32
 endif
 
-spicy_SOURCES =					\
-	spicy.c					\
-	spicy-connect.h 			\
-	spicy-connect.c 			\
-	spice-cmdline.h				\
-	spice-cmdline.c				\
-	$(NULL)
-
-spicy_LDADD =						\
-	libspice-client-gtk-3.0.la			\
-	libspice-client-glib-2.0.la			\
-	$(GTHREAD_LIBS)					\
-	$(GTK_LIBS)					\
-	$(LIBM)						\
-	$(NULL)
-
-# FIXME: GtkAction and lots of GtkUIManager APIs are deprecated
-spicy_CPPFLAGS =			\
-	$(AM_CPPFLAGS)			\
-	$(GTHREAD_CFLAGS)		\
-	-DSPICE_DISABLE_DEPRECATED	\
-	-Wno-deprecated-declarations	\
-	$(NULL)
-
-
 if WITH_POLKIT
 spice_client_glib_usb_acl_helper_SOURCES =	\
 	spice-client-glib-usb-acl-helper.c	\
@@ -436,30 +407,6 @@ install-data-hook:
 endif
 
 
-spicy_screenshot_SOURCES =			\
-	spicy-screenshot.c			\
-	spice-cmdline.h				\
-	spice-cmdline.c				\
-	$(NULL)
-
-spicy_screenshot_LDADD =			\
-	libspice-client-glib-2.0.la		\
-	$(GOBJECT2_LIBS)			\
-	$(NULL)
-
-spicy_stats_SOURCES =			\
-	spicy-stats.c			\
-	spice-cmdline.h			\
-	spice-cmdline.c			\
-	$(NULL)
-
-spicy_stats_LDADD =				\
-	libspice-client-glib-2.0.la		\
-	$(GOBJECT2_LIBS)			\
-	$(NULL)
-
-
-
 $(libspice_client_glib_2_0_la_SOURCES): spice-glib-enums.h spice-marshal.h
 
 if WITH_GTK
diff --git a/src/spice-cmdline.c b/src/spice-cmdline.c
deleted file mode 100644
index 4b6f4c2..0000000
--- a/src/spice-cmdline.c
+++ /dev/null
@@ -1,98 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   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/>.
-*/
-#include "config.h"
-#include <glib/gi18n-lib.h>
-
-#include "spice-client.h"
-#include "spice-common.h"
-#include "spice-cmdline.h"
-
-static char *host;
-static char *port;
-static char *tls_port;
-static char *password;
-static char *uri;
-
-static GOptionEntry spice_entries[] = {
-    {
-        .long_name        = "uri",
-        .arg              = G_OPTION_ARG_STRING,
-        .arg_data         = &uri,
-        .description      = N_("Spice server uri"),
-        .arg_description  = N_("<uri>"),
-    },{
-        .long_name        = "host",
-        .short_name       = 'h',
-        .arg              = G_OPTION_ARG_STRING,
-        .arg_data         = &host,
-        .description      = N_("Spice server address"),
-        .arg_description  = N_("<host>"),
-    },{
-        .long_name        = "port",
-        .short_name       = 'p',
-        .arg              = G_OPTION_ARG_STRING,
-        .arg_data         = &port,
-        .description      = N_("Spice server port"),
-        .arg_description  = N_("<port>"),
-    },{
-        .long_name        = "secure-port",
-        .short_name       = 's',
-        .arg              = G_OPTION_ARG_STRING,
-        .arg_data         = &tls_port,
-        .description      = N_("Spice server secure port"),
-        .arg_description  = N_("<port>"),
-    },{
-        .long_name        = "password",
-        .short_name       = 'w',
-        .arg              = G_OPTION_ARG_STRING,
-        .arg_data         = &password,
-        .description      = N_("Server password"),
-        .arg_description  = N_("<password>"),
-    },{
-        /* end of list */
-    }
-};
-
-GOptionGroup *spice_cmdline_get_option_group(void)
-{
-    GOptionGroup *grp;
-
-    grp = g_option_group_new("spice",
-                             _("Spice connection options:"),
-                             _("Show Spice options"),
-                             NULL, NULL);
-    g_option_group_add_entries(grp, spice_entries);
-
-    return grp;
-}
-
-void spice_cmdline_session_setup(SpiceSession *session)
-{
-    g_return_if_fail(SPICE_IS_SESSION(session));
-
-    if (uri)
-        g_object_set(session, "uri", uri, NULL);
-    if (host)
-        g_object_set(session, "host", host, NULL);
-    if (port)
-        g_object_set(session, "port", port, NULL);
-    if (tls_port)
-        g_object_set(session, "tls-port", tls_port, NULL);
-    if (password)
-        g_object_set(session, "password", password, NULL);
-}
diff --git a/src/spice-cmdline.h b/src/spice-cmdline.h
deleted file mode 100644
index 11a8086..0000000
--- a/src/spice-cmdline.h
+++ /dev/null
@@ -1,29 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   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/>.
-*/
-
-#ifndef SPICE_CMDLINE_H_
-# define SPICE_CMDLINE_H_
-
-G_BEGIN_DECLS
-
-GOptionGroup *spice_cmdline_get_option_group(void);
-void spice_cmdline_session_setup(SpiceSession *session);
-
-G_END_DECLS
-
-#endif // SPICE_CMDLINE_H_
diff --git a/src/spicy-connect.c b/src/spicy-connect.c
deleted file mode 100644
index 39555a6..0000000
--- a/src/spicy-connect.c
+++ /dev/null
@@ -1,248 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2010-2015 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/>.
-*/
-
-#include <gtk/gtk.h>
-#include <gdk/gdkkeysyms.h>
-#include "spice-common.h"
-#include "spicy-connect.h"
-
-typedef struct
-{
-    gboolean connecting;
-    GMainLoop *loop;
-    SpiceSession *session;
-} ConnectionInfo;
-
-static struct {
-    const char *text;
-    const char *prop;
-    GtkWidget *entry;
-} connect_entries[] = {
-    { .text = "Hostname",   .prop = "host"      },
-    { .text = "Port",       .prop = "port"      },
-    { .text = "TLS Port",   .prop = "tls-port"  },
-};
-
-static gboolean can_connect(void)
-{
-    if ((gtk_entry_get_text_length(GTK_ENTRY(connect_entries[0].entry)) > 0) &&
-        ((gtk_entry_get_text_length(GTK_ENTRY(connect_entries[1].entry)) > 0) ||
-         (gtk_entry_get_text_length(GTK_ENTRY(connect_entries[2].entry)) > 0)))
-        return TRUE;
-
-    return FALSE;
-}
-
-static void set_connection_info(SpiceSession *session)
-{
-    const gchar *txt;
-    int i;
-
-    for (i = 0; i < SPICE_N_ELEMENTS(connect_entries); i++) {
-        txt = gtk_entry_get_text(GTK_ENTRY(connect_entries[i].entry));
-        g_object_set(session, connect_entries[i].prop, txt, NULL);
-    }
-}
-
-static gboolean close_cb(gpointer data)
-{
-    ConnectionInfo *info = data;
-    info->connecting = FALSE;
-    if (g_main_loop_is_running(info->loop))
-        g_main_loop_quit(info->loop);
-
-    return TRUE;
-}
-
-static void entry_changed_cb(GtkEditable* entry, gpointer data)
-{
-    GtkButton *connect_button = data;
-    gtk_widget_set_sensitive(GTK_WIDGET(connect_button), can_connect());
-}
-
-static gboolean entry_focus_in_cb(GtkWidget *widget, GdkEvent *event, gpointer data)
-{
-    GtkRecentChooser *recent = GTK_RECENT_CHOOSER(data);
-    gtk_recent_chooser_unselect_all(recent);
-    return TRUE;
-}
-
-static gboolean key_pressed_cb(GtkWidget *widget, GdkEvent *event, gpointer data)
-{
-    gboolean tst;
-    if (event->type == GDK_KEY_PRESS) {
-        switch (event->key.keyval) {
-            case GDK_KEY_Escape:
-                g_signal_emit_by_name(GTK_WIDGET(data), "delete-event", NULL, &tst);
-                return TRUE;
-            default:
-                return FALSE;
-        }
-    }
-
-    return FALSE;
-}
-
-static void recent_selection_changed_dialog_cb(GtkRecentChooser *chooser, gpointer data)
-{
-    GtkRecentInfo *info;
-    gchar *txt = NULL;
-    const gchar *uri;
-    SpiceSession *session = data;
-    int i;
-
-    info = gtk_recent_chooser_get_current_item(chooser);
-    if (info == NULL)
-        return;
-
-    uri = gtk_recent_info_get_uri(info);
-    g_return_if_fail(uri != NULL);
-
-    g_object_set(session, "uri", uri, NULL);
-
-    for (i = 0; i < SPICE_N_ELEMENTS(connect_entries); i++) {
-        g_object_get(session, connect_entries[i].prop, &txt, NULL);
-        gtk_entry_set_text(GTK_ENTRY(connect_entries[i].entry), txt ? txt : "");
-        g_free(txt);
-    }
-
-    gtk_recent_info_unref(info);
-}
-
-static void connect_cb(gpointer data)
-{
-    ConnectionInfo *info = data;
-    if (can_connect())
-    {
-        info->connecting = TRUE;
-        set_connection_info(info->session);
-        if (g_main_loop_is_running(info->loop))
-            g_main_loop_quit(info->loop);
-    }
-}
-
-gboolean spicy_connect_dialog(SpiceSession *session)
-{
-    GtkWidget *connect_button, *cancel_button, *label;
-    GtkBox *main_box, *recent_box, *button_box;
-    GtkWindow *window;
-    GtkGrid *grid;
-    int i;
-
-    ConnectionInfo info = {
-        FALSE,
-        NULL,
-        session
-    };
-
-    /* Create the widgets */
-    window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
-    gtk_window_set_title(window, "Connect to SPICE");
-    gtk_window_set_resizable(window, FALSE);
-    gtk_container_set_border_width(GTK_CONTAINER(window), 5);
-
-    main_box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
-    gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(main_box));
-
-    grid = GTK_GRID(gtk_grid_new());
-    gtk_box_pack_start(main_box, GTK_WIDGET(grid), FALSE, TRUE, 0);
-    gtk_container_set_border_width(GTK_CONTAINER(grid), 5);
-    gtk_grid_set_row_spacing(grid, 5);
-    gtk_grid_set_column_spacing(grid, 5);
-
-    for (i = 0; i < SPICE_N_ELEMENTS(connect_entries); i++) {
-        gchar *txt;
-        label = gtk_label_new(connect_entries[i].text);
-        gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
-        gtk_grid_attach(grid, label, 0, i, 1, 1);
-        connect_entries[i].entry = GTK_WIDGET(gtk_entry_new());
-        gtk_grid_attach(grid, connect_entries[i].entry, 1, i, 1, 1);
-        g_object_get(session, connect_entries[i].prop, &txt, NULL);
-        SPICE_DEBUG("%s: #%i [%s]: \"%s\"",
-                __FUNCTION__, i, connect_entries[i].prop, txt);
-        if (txt) {
-            gtk_entry_set_text(GTK_ENTRY(connect_entries[i].entry), txt);
-            g_free(txt);
-        }
-    }
-
-    recent_box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
-    gtk_box_pack_start(main_box, GTK_WIDGET(recent_box), TRUE, TRUE, 0);
-    gtk_container_set_border_width(GTK_CONTAINER(recent_box), 5);
-
-    label = gtk_label_new("Recent connections:");
-    gtk_box_pack_start(recent_box, label, FALSE, TRUE, 0);
-    gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
-
-    button_box = GTK_BOX(gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL));
-    gtk_button_box_set_layout(GTK_BUTTON_BOX(button_box), GTK_BUTTONBOX_END);
-    gtk_box_set_spacing(button_box, 5);
-    gtk_container_set_border_width(GTK_CONTAINER(button_box), 5);
-    connect_button = gtk_button_new_with_label("Connect");
-    cancel_button = gtk_button_new_with_label("Cancel");
-    gtk_box_pack_start(button_box, cancel_button, FALSE, TRUE, 0);
-    gtk_box_pack_start(button_box, connect_button, FALSE, TRUE, 1);
-
-    gtk_box_pack_start(main_box, GTK_WIDGET(button_box), FALSE, TRUE, 0);
-
-    gtk_widget_set_sensitive(GTK_WIDGET(connect_button), can_connect());
-
-    g_signal_connect(window, "key-press-event",
-                     G_CALLBACK(key_pressed_cb), window);
-    g_signal_connect_swapped(window, "delete-event",
-                             G_CALLBACK(close_cb), &info);
-    g_signal_connect_swapped(connect_button, "clicked",
-                             G_CALLBACK(connect_cb), &info);
-    g_signal_connect_swapped(cancel_button, "clicked",
-                             G_CALLBACK(close_cb), &info);
-
-    GtkRecentFilter *rfilter;
-    GtkWidget *recent;
-
-    recent = GTK_WIDGET(gtk_recent_chooser_widget_new());
-    gtk_recent_chooser_set_show_icons(GTK_RECENT_CHOOSER(recent), FALSE);
-    gtk_box_pack_start(recent_box, recent, TRUE, TRUE, 0);
-
-    rfilter = gtk_recent_filter_new();
-    gtk_recent_filter_add_mime_type(rfilter, "application/x-spice");
-    gtk_recent_chooser_set_filter(GTK_RECENT_CHOOSER(recent), rfilter);
-    gtk_recent_chooser_set_local_only(GTK_RECENT_CHOOSER(recent), FALSE);
-    g_signal_connect(recent, "selection-changed",
-                     G_CALLBACK(recent_selection_changed_dialog_cb), session);
-    g_signal_connect_swapped(recent, "item-activated",
-                             G_CALLBACK(connect_cb), &info);
-
-    for (i = 0; i < SPICE_N_ELEMENTS(connect_entries); i++) {
-        g_signal_connect_swapped(connect_entries[i].entry, "activate",
-                                 G_CALLBACK(connect_cb), &info);
-        g_signal_connect(connect_entries[i].entry, "changed",
-                         G_CALLBACK(entry_changed_cb), connect_button);
-        g_signal_connect(connect_entries[i].entry, "focus-in-event",
-                         G_CALLBACK(entry_focus_in_cb), recent);
-    }
-
-    /* show and wait for response */
-    gtk_widget_show_all(GTK_WIDGET(window));
-
-    info.loop = g_main_loop_new(NULL, FALSE);
-    g_main_loop_run(info.loop);
-
-    gtk_widget_destroy(GTK_WIDGET(window));
-
-    return info.connecting;
-}
diff --git a/src/spicy-connect.h b/src/spicy-connect.h
deleted file mode 100644
index 56b2d80..0000000
--- a/src/spicy-connect.h
+++ /dev/null
@@ -1,26 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2010-2015 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/>.
-*/
-
-#ifndef SPICY_CONNECT_H
-#define SPICY_CONNECT_H
-
-#include "spice-widget.h"
-
-gboolean spicy_connect_dialog(SpiceSession *session);
-
-#endif
diff --git a/src/spicy-screenshot.c b/src/spicy-screenshot.c
deleted file mode 100644
index 68f9335..0000000
--- a/src/spicy-screenshot.c
+++ /dev/null
@@ -1,194 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   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/>.
-*/
-#include "config.h"
-
-#include "spice-client.h"
-#include "spice-common.h"
-#include "spice-cmdline.h"
-
-/* config */
-static const char *outf      = "spicy-screenshot.ppm";
-static gboolean version = FALSE;
-
-/* state */
-static SpiceSession  *session;
-static GMainLoop     *mainloop;
-
-enum SpiceSurfaceFmt d_format;
-gint                 d_width, d_height, d_stride;
-gpointer             d_data;
-
-/* ------------------------------------------------------------------ */
-
-static void primary_create(SpiceChannel *channel, gint format,
-                           gint width, gint height, gint stride,
-                           gint shmid, gpointer imgdata, gpointer data)
-{
-    SPICE_DEBUG("%s: %dx%d, format %d", __FUNCTION__, width, height, format);
-    d_format = format;
-    d_width  = width;
-    d_height = height;
-    d_stride = stride;
-    d_data   = imgdata;
-}
-
-static int write_ppm_32(void)
-{
-    FILE *fp;
-    uint8_t *p;
-    int n;
-
-    fp = fopen(outf,"w");
-    if (NULL == fp) {
-	fprintf(stderr, "%s: can't open %s: %s\n", g_get_prgname(), outf, strerror(errno));
-	return -1;
-    }
-    fprintf(fp, "P6\n%d %d\n255\n",
-            d_width, d_height);
-    n = d_width * d_height;
-    p = d_data;
-    while (n > 0) {
-#ifdef WORDS_BIGENDIAN
-        fputc(p[1], fp);
-        fputc(p[2], fp);
-        fputc(p[3], fp);
-#else
-        fputc(p[2], fp);
-        fputc(p[1], fp);
-        fputc(p[0], fp);
-#endif
-        p += 4;
-        n--;
-    }
-    fclose(fp);
-    return 0;
-}
-
-static void invalidate(SpiceChannel *channel,
-                       gint x, gint y, gint w, gint h, gpointer *data)
-{
-    int rc;
-
-    switch (d_format) {
-    case SPICE_SURFACE_FMT_32_xRGB:
-        rc = write_ppm_32();
-        break;
-    default:
-        fprintf(stderr, "unsupported spice surface format %u\n", d_format);
-        rc = -1;
-        break;
-    }
-    if (rc == 0)
-        fprintf(stderr, "wrote screen shot to %s\n", outf);
-    g_main_loop_quit(mainloop);
-}
-
-static void main_channel_event(SpiceChannel *channel, SpiceChannelEvent event,
-                               gpointer data)
-{
-    switch (event) {
-    case SPICE_CHANNEL_OPENED:
-        break;
-    default:
-        g_warning("main channel event: %u", event);
-        g_main_loop_quit(mainloop);
-    }
-}
-
-static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer *data)
-{
-    int id;
-
-    if (SPICE_IS_MAIN_CHANNEL(channel)) {
-        g_signal_connect(channel, "channel-event",
-                         G_CALLBACK(main_channel_event), data);
-        return;
-    }
-
-    if (!SPICE_IS_DISPLAY_CHANNEL(channel))
-        return;
-
-    g_object_get(channel, "channel-id", &id, NULL);
-    if (id != 0)
-        return;
-
-    g_signal_connect(channel, "display-primary-create",
-                     G_CALLBACK(primary_create), NULL);
-    g_signal_connect(channel, "display-invalidate",
-                     G_CALLBACK(invalidate), NULL);
-    spice_channel_connect(channel);
-}
-
-/* ------------------------------------------------------------------ */
-
-static GOptionEntry app_entries[] = {
-    {
-        .long_name        = "out-file",
-        .short_name       = 'o',
-        .arg              = G_OPTION_ARG_FILENAME,
-        .arg_data         = &outf,
-        .description      = "Output file name (default spicy-screenshot.ppm)",
-        .arg_description  = "<filename>",
-    },
-    {
-        .long_name        = "version",
-        .arg              = G_OPTION_ARG_NONE,
-        .arg_data         = &version,
-        .description      = "Display version and quit",
-    },
-    {
-        /* end of list */
-    }
-};
-
-int main(int argc, char *argv[])
-{
-    GError *error = NULL;
-    GOptionContext *context;
-
-    /* parse opts */
-    context = g_option_context_new(" - make screen shots");
-    g_option_context_set_summary(context, "A Spice server client to take screenshots in ppm format.");
-    g_option_context_set_description(context, "Report bugs to " PACKAGE_BUGREPORT ".");
-    g_option_context_set_main_group(context, spice_cmdline_get_option_group());
-    g_option_context_add_main_entries(context, app_entries, NULL);
-    if (!g_option_context_parse (context, &argc, &argv, &error)) {
-        g_print("option parsing failed: %s\n", error->message);
-        exit(1);
-    }
-
-    if (version) {
-        g_print("%s " PACKAGE_VERSION "\n", g_get_prgname());
-        exit(0);
-    }
-
-    mainloop = g_main_loop_new(NULL, false);
-
-    session = spice_session_new();
-    g_signal_connect(session, "channel-new",
-                     G_CALLBACK(channel_new), NULL);
-    spice_cmdline_session_setup(session);
-
-    if (!spice_session_connect(session)) {
-        fprintf(stderr, "spice_session_connect failed\n");
-        exit(1);
-    }
-
-    g_main_loop_run(mainloop);
-    return 0;
-}
diff --git a/src/spicy-stats.c b/src/spicy-stats.c
deleted file mode 100644
index 8ca4cc1..0000000
--- a/src/spicy-stats.c
+++ /dev/null
@@ -1,136 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   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/>.
-*/
-#include "config.h"
-
-#include "spice-client.h"
-#include "spice-common.h"
-#include "spice-cmdline.h"
-
-/* config */
-static gboolean version = FALSE;
-
-/* state */
-static SpiceSession  *session;
-static GMainLoop     *mainloop;
-
-/* ------------------------------------------------------------------ */
-static void main_channel_event(SpiceChannel *channel, SpiceChannelEvent event,
-                               gpointer data)
-{
-    switch (event) {
-    case SPICE_CHANNEL_OPENED:
-        break;
-    default:
-        g_warning("main channel event: %u", event);
-        g_main_loop_quit(mainloop);
-    }
-}
-
-static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer *data)
-{
-    int id;
-
-    if (SPICE_IS_MAIN_CHANNEL(channel)) {
-        SPICE_DEBUG("new main channel");
-        g_signal_connect(channel, "channel-event",
-                         G_CALLBACK(main_channel_event), data);
-    }
-
-    if (SPICE_IS_DISPLAY_CHANNEL(channel)) {
-        g_object_get(channel, "channel-id", &id, NULL);
-        if (id != 0)
-            return;
-    }
-
-    spice_channel_connect(channel);
-}
-
-/* ------------------------------------------------------------------ */
-
-static GOptionEntry app_entries[] = {
-    {
-        .long_name        = "version",
-        .arg              = G_OPTION_ARG_NONE,
-        .arg_data         = &version,
-        .description      = "Display version and quit",
-    },
-    {
-        /* end of list */
-    }
-};
-
-static void
-signal_handler(int signum)
-{
-    g_main_loop_quit(mainloop);
-}
-
-int main(int argc, char *argv[])
-{
-    GError *error = NULL;
-    GOptionContext *context;
-
-    signal(SIGINT, signal_handler);
-
-    /* parse opts */
-    context = g_option_context_new(NULL);
-    g_option_context_set_summary(context, "A Spice client used for testing and measurements.");
-    g_option_context_set_description(context, "Report bugs to " PACKAGE_BUGREPORT ".");
-    g_option_context_set_main_group(context, spice_cmdline_get_option_group());
-    g_option_context_add_main_entries(context, app_entries, NULL);
-    if (!g_option_context_parse (context, &argc, &argv, &error)) {
-        g_print("option parsing failed: %s\n", error->message);
-        exit(1);
-    }
-
-    if (version) {
-        g_print("spicy-stats " PACKAGE_VERSION "\n");
-        exit(0);
-    }
-
-    mainloop = g_main_loop_new(NULL, false);
-
-    session = spice_session_new();
-    g_signal_connect(session, "channel-new",
-                     G_CALLBACK(channel_new), NULL);
-    spice_cmdline_session_setup(session);
-
-    if (!spice_session_connect(session)) {
-        fprintf(stderr, "spice_session_connect failed\n");
-        exit(1);
-    }
-
-    g_main_loop_run(mainloop);
-    {
-        GList *iter, *list = spice_session_get_channels(session);
-        gulong total_read_bytes;
-        gint  channel_type;
-        printf("total bytes read:\n");
-        for (iter = list ; iter ; iter = iter->next) {
-            g_object_get(iter->data,
-                "total-read-bytes", &total_read_bytes,
-                "channel-type", &channel_type,
-                NULL);
-            printf("%s: %lu\n",
-                   spice_channel_type_to_string(channel_type),
-                   total_read_bytes);
-        }
-        g_list_free(list);
-    }
-    return 0;
-}
diff --git a/src/spicy.c b/src/spicy.c
deleted file mode 100644
index c502428..0000000
--- a/src/spicy.c
+++ /dev/null
@@ -1,1938 +0,0 @@
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
-   Copyright (C) 2010-2011 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/>.
-*/
-
-#include "config.h"
-#include <glib.h>
-
-#include <sys/stat.h>
-#ifdef HAVE_TERMIOS_H
-#include <termios.h>
-#endif
-
-#ifdef USE_SMARTCARD_012
-#include <vreader.h>
-#endif
-
-#include "spice-widget.h"
-#include "spice-gtk-session.h"
-#include "spice-audio.h"
-#include "spice-common.h"
-#include "spice-cmdline.h"
-#include "spice-option.h"
-#include "usb-device-widget.h"
-
-#include "spicy-connect.h"
-
-typedef struct spice_connection spice_connection;
-
-enum {
-    STATE_SCROLL_LOCK,
-    STATE_CAPS_LOCK,
-    STATE_NUM_LOCK,
-    STATE_MAX,
-};
-
-#define SPICE_TYPE_WINDOW                  (spice_window_get_type ())
-#define SPICE_WINDOW(obj)                  (G_TYPE_CHECK_INSTANCE_CAST ((obj), SPICE_TYPE_WINDOW, SpiceWindow))
-#define SPICE_IS_WINDOW(obj)               (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SPICE_TYPE_WINDOW))
-#define SPICE_WINDOW_CLASS(klass)          (G_TYPE_CHECK_CLASS_CAST ((klass), SPICE_TYPE_WINDOW, SpiceWindowClass))
-#define SPICE_IS_WINDOW_CLASS(klass)       (G_TYPE_CHECK_CLASS_TYPE ((klass), SPICE_TYPE_WINDOW))
-#define SPICE_WINDOW_GET_CLASS(obj)        (G_TYPE_INSTANCE_GET_CLASS ((obj), SPICE_TYPE_WINDOW, SpiceWindowClass))
-
-typedef struct _SpiceWindow SpiceWindow;
-typedef struct _SpiceWindowClass SpiceWindowClass;
-
-struct _SpiceWindow {
-    GObject          object;
-    spice_connection *conn;
-    gint             id;
-    gint             monitor_id;
-    GtkWidget        *toplevel, *spice;
-    GtkWidget        *menubar, *toolbar;
-    GtkWidget        *ritem, *rmenu;
-    GtkWidget        *statusbar, *status, *st[STATE_MAX];
-    GtkActionGroup   *ag;
-    GtkUIManager     *ui;
-    bool             fullscreen;
-    bool             mouse_grabbed;
-    SpiceChannel     *display_channel;
-#ifdef G_OS_WIN32
-    gint             win_x;
-    gint             win_y;
-#endif
-    bool             enable_accels_save;
-    bool             enable_mnemonics_save;
-};
-
-struct _SpiceWindowClass
-{
-  GObjectClass parent_class;
-};
-
-static GType spice_window_get_type(void);
-
-G_DEFINE_TYPE (SpiceWindow, spice_window, G_TYPE_OBJECT);
-
-#define CHANNELID_MAX 4
-#define MONITORID_MAX 4
-
-
-// FIXME: turn this into an object, get rid of fixed wins array, use
-// signals to replace the various callback that iterate over wins array
-struct spice_connection {
-    SpiceSession     *session;
-    SpiceGtkSession  *gtk_session;
-    SpiceMainChannel *main;
-    SpiceWindow     *wins[CHANNELID_MAX * MONITORID_MAX];
-    SpiceAudio       *audio;
-    const char       *mouse_state;
-    const char       *agent_state;
-    gboolean         agent_connected;
-    int              channels;
-    int              disconnecting;
-
-    /* key: SpiceFileTransferTask, value: TransferTaskWidgets */
-    GHashTable *transfers;
-    GtkWidget *transfer_dialog;
-};
-
-static spice_connection *connection_new(void);
-static void connection_connect(spice_connection *conn);
-static void connection_disconnect(spice_connection *conn);
-static void connection_destroy(spice_connection *conn);
-static void usb_connect_failed(GObject               *object,
-                               SpiceUsbDevice        *device,
-                               GError                *error,
-                               gpointer               data);
-static gboolean is_gtk_session_property(const gchar *property);
-static void del_window(spice_connection *conn, SpiceWindow *win);
-
-/* options */
-static gboolean fullscreen = false;
-static gboolean version = false;
-static char *spicy_title = NULL;
-/* globals */
-static GMainLoop     *mainloop = NULL;
-static int           connections = 0;
-static GKeyFile      *keyfile = NULL;
-static SpicePortChannel*stdin_port = NULL;
-
-/* ------------------------------------------------------------------ */
-
-static int ask_user(GtkWidget *parent, char *title, char *message,
-                    char *dest, int dlen, int hide)
-{
-    GtkWidget *dialog, *area, *label, *entry;
-    const char *txt;
-    int retval;
-
-    /* Create the widgets */
-    dialog = gtk_dialog_new_with_buttons(title,
-                                         parent ? GTK_WINDOW(parent) : NULL,
-                                         GTK_DIALOG_DESTROY_WITH_PARENT,
-                                         "_OK",
-                                         GTK_RESPONSE_ACCEPT,
-                                         "_Cancel",
-                                         GTK_RESPONSE_REJECT,
-                                         NULL);
-    gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
-    area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
-
-    label = gtk_label_new(message);
-    gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
-    gtk_box_pack_start(GTK_BOX(area), label, FALSE, FALSE, 5);
-
-    entry = gtk_entry_new();
-    gtk_entry_set_text(GTK_ENTRY(entry), dest);
-    gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
-    if (hide)
-        gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
-    gtk_box_pack_start(GTK_BOX(area), entry, FALSE, FALSE, 5);
-
-    /* show and wait for response */
-    gtk_widget_show_all(dialog);
-    switch (gtk_dialog_run(GTK_DIALOG(dialog))) {
-    case GTK_RESPONSE_ACCEPT:
-        txt = gtk_entry_get_text(GTK_ENTRY(entry));
-        snprintf(dest, dlen, "%s", txt);
-        retval = 0;
-        break;
-    default:
-        retval = -1;
-        break;
-    }
-    gtk_widget_destroy(dialog);
-    return retval;
-}
-
-static void update_status_window(SpiceWindow *win)
-{
-    gchar *status;
-
-    if (win == NULL)
-        return;
-
-    if (win->mouse_grabbed) {
-        SpiceGrabSequence *sequence = spice_display_get_grab_keys(SPICE_DISPLAY(win->spice));
-        gchar *seq = spice_grab_sequence_as_string(sequence);
-        status = g_strdup_printf("Use %s to ungrab mouse.", seq);
-        g_free(seq);
-    } else {
-        status = g_strdup_printf("mouse: %s, agent: %s",
-                 win->conn->mouse_state, win->conn->agent_state);
-    }
-
-    gtk_label_set_text(GTK_LABEL(win->status), status);
-    g_free(status);
-}
-
-static void update_status(struct spice_connection *conn)
-{
-    int i;
-
-    for (i = 0; i < SPICE_N_ELEMENTS(conn->wins); i++) {
-        if (conn->wins[i] == NULL)
-            continue;
-        update_status_window(conn->wins[i]);
-    }
-}
-
-static const char *spice_edit_properties[] = {
-    "CopyToGuest",
-    "PasteFromGuest",
-};
-
-static void update_edit_menu_window(SpiceWindow *win)
-{
-    int i;
-    GtkAction *toggle;
-
-    if (win == NULL) {
-        return;
-    }
-
-    /* Make "CopyToGuest" and "PasteFromGuest" insensitive if spice
-     * agent is not connected */
-    for (i = 0; i < G_N_ELEMENTS(spice_edit_properties); i++) {
-        toggle = gtk_action_group_get_action(win->ag, spice_edit_properties[i]);
-        if (toggle) {
-            gtk_action_set_sensitive(toggle, win->conn->agent_connected);
-        }
-    }
-}
-
-static void update_edit_menu(struct spice_connection *conn)
-{
-    int i;
-
-    for (i = 0; i < SPICE_N_ELEMENTS(conn->wins); i++) {
-        if (conn->wins[i]) {
-            update_edit_menu_window(conn->wins[i]);
-        }
-    }
-}
-
-static void menu_cb_connect(GtkAction *action, void *data)
-{
-    struct spice_connection *conn;
-
-    conn = connection_new();
-    connection_connect(conn);
-}
-
-static void menu_cb_close(GtkAction *action, void *data)
-{
-    SpiceWindow *win = data;
-
-    connection_disconnect(win->conn);
-}
-
-static void menu_cb_copy(GtkAction *action, void *data)
-{
-    SpiceWindow *win = data;
-
-    spice_gtk_session_copy_to_guest(win->conn->gtk_session);
-}
-
-static void menu_cb_paste(GtkAction *action, void *data)
-{
-    SpiceWindow *win = data;
-
-    spice_gtk_session_paste_from_guest(win->conn->gtk_session);
-}
-
-static void window_set_fullscreen(SpiceWindow *win, gboolean fs)
-{
-    if (fs) {
-#ifdef G_OS_WIN32
-        gtk_window_get_position(GTK_WINDOW(win->toplevel), &win->win_x, &win->win_y);
-#endif
-        gtk_window_fullscreen(GTK_WINDOW(win->toplevel));
-    } else {
-        gtk_window_unfullscreen(GTK_WINDOW(win->toplevel));
-#ifdef G_OS_WIN32
-        gtk_window_move(GTK_WINDOW(win->toplevel), win->win_x, win->win_y);
-#endif
-    }
-}
-
-static void menu_cb_fullscreen(GtkAction *action, void *data)
-{
-    SpiceWindow *win = data;
-
-    window_set_fullscreen(win, !win->fullscreen);
-}
-
-#ifdef USE_SMARTCARD
-static void enable_smartcard_actions(SpiceWindow *win, VReader *reader,
-                                     gboolean can_insert, gboolean can_remove)
-{
-    GtkAction *action;
-
-    if ((reader != NULL) && (!spice_smartcard_reader_is_software((SpiceSmartcardReader*)reader)))
-    {
-        /* Having menu actions to insert/remove smartcards only makes sense
-         * for software smartcard readers, don't do anything when the event
-         * we received was for a "real" smartcard reader.
-         */
-        return;
-    }
-    action = gtk_action_group_get_action(win->ag, "InsertSmartcard");
-    g_return_if_fail(action != NULL);
-    gtk_action_set_sensitive(action, can_insert);
-    action = gtk_action_group_get_action(win->ag, "RemoveSmartcard");
-    g_return_if_fail(action != NULL);
-    gtk_action_set_sensitive(action, can_remove);
-}
-
-
-static void reader_added_cb(SpiceSmartcardManager *manager, VReader *reader,
-                            gpointer user_data)
-{
-    enable_smartcard_actions(user_data, reader, TRUE, FALSE);
-}
-
-static void reader_removed_cb(SpiceSmartcardManager *manager, VReader *reader,
-                              gpointer user_data)
-{
-    enable_smartcard_actions(user_data, reader, FALSE, FALSE);
-}
-
-static void card_inserted_cb(SpiceSmartcardManager *manager, VReader *reader,
-                             gpointer user_data)
-{
-    enable_smartcard_actions(user_data, reader, FALSE, TRUE);
-}
-
-static void card_removed_cb(SpiceSmartcardManager *manager, VReader *reader,
-                            gpointer user_data)
-{
-    enable_smartcard_actions(user_data, reader, TRUE, FALSE);
-}
-
-static void menu_cb_insert_smartcard(GtkAction *action, void *data)
-{
-    spice_smartcard_manager_insert_card(spice_smartcard_manager_get());
-}
-
-static void menu_cb_remove_smartcard(GtkAction *action, void *data)
-{
-    spice_smartcard_manager_remove_card(spice_smartcard_manager_get());
-}
-#endif
-
-static void menu_cb_mouse_mode(GtkAction *action, void *data)
-{
-    SpiceWindow *win = data;
-    SpiceMainChannel *cmain = win->conn->main;
-    int mode;
-
-    g_object_get(cmain, "mouse-mode", &mode, NULL);
-    if (mode == SPICE_MOUSE_MODE_CLIENT)
-        mode = SPICE_MOUSE_MODE_SERVER;
-    else
-        mode = SPICE_MOUSE_MODE_CLIENT;
-
-    spice_main_request_mouse_mode(cmain, mode);
-}
-
-#ifdef USE_USBREDIR
-static void remove_cb(GtkContainer *container, GtkWidget *widget, void *data)
-{
-    gtk_window_resize(GTK_WINDOW(data), 1, 1);
-}
-
-static void menu_cb_select_usb_devices(GtkAction *action, void *data)
-{
-    GtkWidget *dialog, *area, *usb_device_widget;
-    SpiceWindow *win = data;
-
-    /* Create the widgets */
-    dialog = gtk_dialog_new_with_buttons(
-                    "Select USB devices for redirection",
-                    GTK_WINDOW(win->toplevel),
-                    GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
-                    "_Close", GTK_RESPONSE_ACCEPT,
-                    NULL);
-    gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
-    gtk_container_set_border_width(GTK_CONTAINER(dialog), 12);
-    gtk_box_set_spacing(GTK_BOX(gtk_bin_get_child(GTK_BIN(dialog))), 12);
-
-    area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
-
-    usb_device_widget = spice_usb_device_widget_new(win->conn->session,
-                                                    NULL); /* default format */
-    g_signal_connect(usb_device_widget, "connect-failed",
-                     G_CALLBACK(usb_connect_failed), NULL);
-    gtk_box_pack_start(GTK_BOX(area), usb_device_widget, TRUE, TRUE, 0);
-
-    /* This shrinks the dialog when USB devices are unplugged */
-    g_signal_connect(usb_device_widget, "remove",
-                     G_CALLBACK(remove_cb), dialog);
-
-    /* show and run */
-    gtk_widget_show_all(dialog);
-    gtk_dialog_run(GTK_DIALOG(dialog));
-    gtk_widget_destroy(dialog);
-}
-#endif
-
-static void menu_cb_bool_prop(GtkToggleAction *action, gpointer data)
-{
-    SpiceWindow *win = data;
-    gboolean state = gtk_toggle_action_get_active(action);
-    const char *name;
-    gpointer object;
-
-    name = gtk_action_get_name(GTK_ACTION(action));
-    SPICE_DEBUG("%s: %s = %s", __FUNCTION__, name, state ? "yes" : "no");
-
-    g_key_file_set_boolean(keyfile, "general", name, state);
-
-    if (is_gtk_session_property(name)) {
-        object = win->conn->gtk_session;
-    } else {
-        object = win->spice;
-    }
-    g_object_set(object, name, state, NULL);
-}
-
-static void menu_cb_conn_bool_prop_changed(GObject    *gobject,
-                                           GParamSpec *pspec,
-                                           gpointer    user_data)
-{
-    SpiceWindow *win = user_data;
-    const gchar *property = g_param_spec_get_name(pspec);
-    GtkAction *toggle;
-    gboolean state;
-
-    toggle = gtk_action_group_get_action(win->ag, property);
-    g_object_get(win->conn->gtk_session, property, &state, NULL);
-    gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(toggle), state);
-}
-
-static void menu_cb_toolbar(GtkToggleAction *action, gpointer data)
-{
-    SpiceWindow *win = data;
-    gboolean state = gtk_toggle_action_get_active(action);
-
-    gtk_widget_set_visible(win->toolbar, state);
-    g_key_file_set_boolean(keyfile, "ui", "toolbar", state);
-}
-
-static void menu_cb_statusbar(GtkToggleAction *action, gpointer data)
-{
-    SpiceWindow *win = data;
-    gboolean state = gtk_toggle_action_get_active(action);
-
-    gtk_widget_set_visible(win->statusbar, state);
-    g_key_file_set_boolean(keyfile, "ui", "statusbar", state);
-}
-
-static void menu_cb_about(GtkAction *action, void *data)
-{
-    char *comments = "gtk test client app for the\n"
-        "spice remote desktop protocol";
-    static const char *copyright = "(c) 2010 Red Hat";
-    static const char *website = "http://www.spice-space.org";
-    static const char *authors[] = { "Gerd Hoffmann <kraxel at redhat.com>",
-                               "Marc-André Lureau <marcandre.lureau at redhat.com>",
-                               NULL };
-    SpiceWindow *win = data;
-
-    gtk_show_about_dialog(GTK_WINDOW(win->toplevel),
-                          "authors",         authors,
-                          "comments",        comments,
-                          "copyright",       copyright,
-                          "logo-icon-name",  "help-about",
-                          "website",         website,
-                          "version",         PACKAGE_VERSION,
-                          "license",         "LGPLv2.1",
-                          NULL);
-}
-
-static gboolean delete_cb(GtkWidget *widget, GdkEvent *event, gpointer data)
-{
-    SpiceWindow *win = data;
-
-    if (win->monitor_id == 0)
-        connection_disconnect(win->conn);
-    else
-        del_window(win->conn, win);
-
-    return true;
-}
-
-static gboolean window_state_cb(GtkWidget *widget, GdkEventWindowState *event,
-                                gpointer data)
-{
-    SpiceWindow *win = data;
-    if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) {
-        win->fullscreen = event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN;
-        if (win->fullscreen) {
-            gtk_widget_hide(win->menubar);
-            gtk_widget_hide(win->toolbar);
-            gtk_widget_hide(win->statusbar);
-            gtk_widget_grab_focus(win->spice);
-        } else {
-            gboolean state;
-            GtkAction *toggle;
-
-            gtk_widget_show(win->menubar);
-            toggle = gtk_action_group_get_action(win->ag, "Toolbar");
-            state = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(toggle));
-            gtk_widget_set_visible(win->toolbar, state);
-            toggle = gtk_action_group_get_action(win->ag, "Statusbar");
-            state = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(toggle));
-            gtk_widget_set_visible(win->statusbar, state);
-        }
-    }
-    return TRUE;
-}
-
-static void grab_keys_pressed_cb(GtkWidget *widget, gpointer data)
-{
-    SpiceWindow *win = data;
-
-    /* since mnemonics are disabled, we leave fullscreen when
-       ungrabbing mouse. Perhaps we should have a different handling
-       of fullscreen key, or simply use a UI, like vinagre */
-    window_set_fullscreen(win, FALSE);
-}
-
-static void mouse_grab_cb(GtkWidget *widget, gint grabbed, gpointer data)
-{
-    SpiceWindow *win = data;
-
-    win->mouse_grabbed = grabbed;
-    update_status(win->conn);
-}
-
-static void keyboard_grab_cb(GtkWidget *widget, gint grabbed, gpointer data)
-{
-    SpiceWindow *win = data;
-    GtkSettings *settings = gtk_widget_get_settings (widget);
-
-    if (grabbed) {
-        /* disable mnemonics & accels */
-        g_object_get(settings,
-                     "gtk-enable-accels", &win->enable_accels_save,
-                     "gtk-enable-mnemonics", &win->enable_mnemonics_save,
-                     NULL);
-        g_object_set(settings,
-                     "gtk-enable-accels", FALSE,
-                     "gtk-enable-mnemonics", FALSE,
-                     NULL);
-    } else {
-        g_object_set(settings,
-                     "gtk-enable-accels", win->enable_accels_save,
-                     "gtk-enable-mnemonics", win->enable_mnemonics_save,
-                     NULL);
-    }
-}
-
-static void restore_configuration(SpiceWindow *win)
-{
-    gboolean state;
-    gchar *str;
-    gchar **keys = NULL;
-    gsize nkeys, i;
-    GError *error = NULL;
-    gpointer object;
-
-    keys = g_key_file_get_keys(keyfile, "general", &nkeys, &error);
-    if (error != NULL) {
-        if (error->code != G_KEY_FILE_ERROR_GROUP_NOT_FOUND)
-            g_warning("Failed to read configuration file keys: %s", error->message);
-        g_clear_error(&error);
-        return;
-    }
-
-    if (nkeys > 0)
-        g_return_if_fail(keys != NULL);
-
-    for (i = 0; i < nkeys; ++i) {
-        if (g_str_equal(keys[i], "grab-sequence"))
-            continue;
-        state = g_key_file_get_boolean(keyfile, "general", keys[i], &error);
-        if (error != NULL) {
-            g_clear_error(&error);
-            continue;
-        }
-
-        if (is_gtk_session_property(keys[i])) {
-            object = win->conn->gtk_session;
-        } else {
-            object = win->spice;
-        }
-        g_object_set(object, keys[i], state, NULL);
-    }
-
-    g_strfreev(keys);
-
-    str = g_key_file_get_string(keyfile, "general", "grab-sequence", &error);
-    if (error == NULL) {
-        SpiceGrabSequence *seq = spice_grab_sequence_new_from_string(str);
-        spice_display_set_grab_keys(SPICE_DISPLAY(win->spice), seq);
-        spice_grab_sequence_free(seq);
-        g_free(str);
-    }
-    g_clear_error(&error);
-
-
-    state = g_key_file_get_boolean(keyfile, "ui", "toolbar", &error);
-    if (error == NULL)
-        gtk_widget_set_visible(win->toolbar, state);
-    g_clear_error(&error);
-
-    state = g_key_file_get_boolean(keyfile, "ui", "statusbar", &error);
-    if (error == NULL)
-        gtk_widget_set_visible(win->statusbar, state);
-    g_clear_error(&error);
-}
-
-/* ------------------------------------------------------------------ */
-
-static const GtkActionEntry entries[] = {
-    {
-        .name        = "FileMenu",
-        .label       = "_File",
-    },{
-        .name        = "FileRecentMenu",
-        .label       = "_Recent",
-    },{
-        .name        = "EditMenu",
-        .label       = "_Edit",
-    },{
-        .name        = "ViewMenu",
-        .label       = "_View",
-    },{
-        .name        = "InputMenu",
-        .label       = "_Input",
-    },{
-        .name        = "OptionMenu",
-        .label       = "_Options",
-    },{
-        .name        = "CompressionMenu",
-        .label       = "_Preferred image compression",
-    },{
-        .name        = "HelpMenu",
-        .label       = "_Help",
-    },{
-
-        /* File menu */
-        .name        = "Connect",
-        .stock_id    = "_Connect",
-        .label       = "_Connect ...",
-        .callback    = G_CALLBACK(menu_cb_connect),
-    },{
-        .name        = "Close",
-        .stock_id    = "window-close",
-        .label       = "_Close",
-        .callback    = G_CALLBACK(menu_cb_close),
-        .accelerator = "", /* none (disable default "<control>W") */
-    },{
-
-        /* Edit menu */
-        .name        = "CopyToGuest",
-        .stock_id    = "edit-copy",
-        .label       = "_Copy to guest",
-        .callback    = G_CALLBACK(menu_cb_copy),
-        .accelerator = "", /* none (disable default "<control>C") */
-    },{
-        .name        = "PasteFromGuest",
-        .stock_id    = "edit-paste",
-        .label       = "_Paste from guest",
-        .callback    = G_CALLBACK(menu_cb_paste),
-        .accelerator = "", /* none (disable default "<control>V") */
-    },{
-
-        /* View menu */
-        .name        = "Fullscreen",
-        .stock_id    = "view-fullscreen",
-        .label       = "_Fullscreen",
-        .callback    = G_CALLBACK(menu_cb_fullscreen),
-        .accelerator = "<shift>F11",
-    },{
-#ifdef USE_SMARTCARD
-	.name        = "InsertSmartcard",
-	.label       = "_Insert Smartcard",
-	.callback    = G_CALLBACK(menu_cb_insert_smartcard),
-        .accelerator = "<shift>F8",
-    },{
-	.name        = "RemoveSmartcard",
-	.label       = "_Remove Smartcard",
-	.callback    = G_CALLBACK(menu_cb_remove_smartcard),
-        .accelerator = "<shift>F9",
-    },{
-#endif
-
-#ifdef USE_USBREDIR
-        .name        = "SelectUsbDevices",
-        .label       = "_Select USB Devices for redirection",
-        .callback    = G_CALLBACK(menu_cb_select_usb_devices),
-        .accelerator = "<shift>F10",
-    },{
-#endif
-
-        .name        = "MouseMode",
-        .label       = "Toggle _mouse mode",
-        .callback    = G_CALLBACK(menu_cb_mouse_mode),
-        .accelerator = "<shift>F7",
-
-    },{
-        /* Help menu */
-        .name        = "About",
-        .stock_id    = "help-about",
-        .label       = "_About ...",
-        .callback    = G_CALLBACK(menu_cb_about),
-    }
-};
-
-static const char *spice_display_properties[] = {
-    "grab-keyboard",
-    "grab-mouse",
-    "resize-guest",
-    "scaling",
-    "disable-inputs",
-};
-
-static const char *spice_gtk_session_properties[] = {
-    "auto-clipboard",
-    "auto-usbredir",
-    "sync-modifiers",
-};
-
-static const GtkToggleActionEntry tentries[] = {
-    {
-        .name        = "grab-keyboard",
-        .label       = "Grab keyboard when active and focused",
-        .callback    = G_CALLBACK(menu_cb_bool_prop),
-    },{
-        .name        = "grab-mouse",
-        .label       = "Grab mouse in server mode (no tablet/vdagent)",
-        .callback    = G_CALLBACK(menu_cb_bool_prop),
-    },{
-        .name        = "resize-guest",
-        .label       = "Resize guest to match window size",
-        .callback    = G_CALLBACK(menu_cb_bool_prop),
-    },{
-        .name        = "scaling",
-        .label       = "Scale display",
-        .callback    = G_CALLBACK(menu_cb_bool_prop),
-    },{
-        .name        = "disable-inputs",
-        .label       = "Disable inputs",
-        .callback    = G_CALLBACK(menu_cb_bool_prop),
-    },{
-        .name        = "sync-modifiers",
-        .label       = "Sync modifiers",
-        .callback    = G_CALLBACK(menu_cb_bool_prop),
-    },{
-        .name        = "auto-clipboard",
-        .label       = "Automatic clipboard sharing between host and guest",
-        .callback    = G_CALLBACK(menu_cb_bool_prop),
-    },{
-        .name        = "auto-usbredir",
-        .label       = "Auto redirect newly plugged in USB devices",
-        .callback    = G_CALLBACK(menu_cb_bool_prop),
-    },{
-        .name        = "Statusbar",
-        .label       = "Statusbar",
-        .callback    = G_CALLBACK(menu_cb_statusbar),
-    },{
-        .name        = "Toolbar",
-        .label       = "Toolbar",
-        .callback    = G_CALLBACK(menu_cb_toolbar),
-    }
-};
-
-static const GtkRadioActionEntry compression_entries[] = {
-    {
-        .name  = "auto-glz",
-        .label = "auto-glz",
-        .value = SPICE_IMAGE_COMPRESSION_AUTO_GLZ,
-    },{
-        .name  = "auto-lz",
-        .label = "auto-lz",
-        .value = SPICE_IMAGE_COMPRESSION_AUTO_LZ,
-    },{
-        .name  = "quic",
-        .label = "quic",
-        .value = SPICE_IMAGE_COMPRESSION_QUIC,
-    },{
-        .name  = "glz",
-        .label = "glz",
-        .value = SPICE_IMAGE_COMPRESSION_GLZ,
-    },{
-        .name  = "lz",
-        .label = "lz",
-        .value = SPICE_IMAGE_COMPRESSION_LZ,
-    },{
-#ifdef USE_LZ4
-        .name  = "lz4",
-        .label = "lz4",
-        .value = SPICE_IMAGE_COMPRESSION_LZ4,
-    },{
-#endif
-        .name  = "off",
-        .label = "off",
-        .value = SPICE_IMAGE_COMPRESSION_OFF,
-    }
-};
-
-static char ui_xml[] =
-"<ui>\n"
-"  <menubar action='MainMenu'>\n"
-"    <menu action='FileMenu'>\n"
-"      <menuitem action='Connect'/>\n"
-"      <menu action='FileRecentMenu'/>\n"
-"      <separator/>\n"
-"      <menuitem action='Close'/>\n"
-"    </menu>\n"
-"    <menu action='EditMenu'>\n"
-"      <menuitem action='CopyToGuest'/>\n"
-"      <menuitem action='PasteFromGuest'/>\n"
-"    </menu>\n"
-"    <menu action='ViewMenu'>\n"
-"      <menuitem action='Fullscreen'/>\n"
-"      <menuitem action='Toolbar'/>\n"
-"      <menuitem action='Statusbar'/>\n"
-"    </menu>\n"
-"    <menu action='InputMenu'>\n"
-#ifdef USE_SMARTCARD
-"      <menuitem action='InsertSmartcard'/>\n"
-"      <menuitem action='RemoveSmartcard'/>\n"
-#endif
-#ifdef USE_USBREDIR
-"      <menuitem action='SelectUsbDevices'/>\n"
-#endif
-"    </menu>\n"
-"    <menu action='OptionMenu'>\n"
-"      <menuitem action='grab-keyboard'/>\n"
-"      <menuitem action='grab-mouse'/>\n"
-"      <menuitem action='MouseMode'/>\n"
-"      <menuitem action='resize-guest'/>\n"
-"      <menuitem action='scaling'/>\n"
-"      <menuitem action='disable-inputs'/>\n"
-"      <menuitem action='sync-modifiers'/>\n"
-"      <menuitem action='auto-clipboard'/>\n"
-"      <menuitem action='auto-usbredir'/>\n"
-"      <menu action='CompressionMenu'>\n"
-"        <menuitem action='auto-glz'/>\n"
-"        <menuitem action='auto-lz'/>\n"
-"        <menuitem action='quic'/>\n"
-"        <menuitem action='glz'/>\n"
-"        <menuitem action='lz'/>\n"
-#ifdef USE_LZ4
-"        <menuitem action='lz4'/>\n"
-#endif
-"        <menuitem action='off'/>\n"
-"      </menu>\n"
-"    </menu>\n"
-"    <menu action='HelpMenu'>\n"
-"      <menuitem action='About'/>\n"
-"    </menu>\n"
-"  </menubar>\n"
-"  <toolbar action='ToolBar'>\n"
-"    <toolitem action='Close'/>\n"
-"    <separator/>\n"
-"    <toolitem action='CopyToGuest'/>\n"
-"    <toolitem action='PasteFromGuest'/>\n"
-"    <separator/>\n"
-"    <toolitem action='Fullscreen'/>\n"
-"  </toolbar>\n"
-"</ui>\n";
-
-static gboolean is_gtk_session_property(const gchar *property)
-{
-    int i;
-
-    for (i = 0; i < G_N_ELEMENTS(spice_gtk_session_properties); i++) {
-        if (!strcmp(spice_gtk_session_properties[i], property)) {
-            return TRUE;
-        }
-    }
-    return FALSE;
-}
-
-static void recent_item_activated_cb(GtkRecentChooser *chooser, gpointer data)
-{
-    GtkRecentInfo *info;
-    struct spice_connection *conn;
-    const char *uri;
-
-    info = gtk_recent_chooser_get_current_item(chooser);
-
-    uri = gtk_recent_info_get_uri(info);
-    g_return_if_fail(uri != NULL);
-
-    conn = connection_new();
-    g_object_set(conn->session, "uri", uri, NULL);
-    gtk_recent_info_unref(info);
-    connection_connect(conn);
-}
-
-static void compression_cb(GtkRadioAction *action G_GNUC_UNUSED,
-                           GtkRadioAction *current,
-                           gpointer user_data)
-{
-    spice_display_change_preferred_compression(SPICE_CHANNEL(user_data),
-                                               gtk_radio_action_get_current_value(current));
-}
-
-static void
-spice_window_class_init (SpiceWindowClass *klass)
-{
-}
-
-static void
-spice_window_init (SpiceWindow *self)
-{
-}
-
-static SpiceWindow *create_spice_window(spice_connection *conn, SpiceChannel *channel, int id, gint monitor_id)
-{
-    char title[32];
-    SpiceWindow *win;
-    GtkAction *toggle;
-    gboolean state;
-    GtkWidget *vbox, *frame;
-    GError *err = NULL;
-    int i;
-    SpiceGrabSequence *seq;
-
-    win = g_object_new(SPICE_TYPE_WINDOW, NULL);
-    win->id = id;
-    win->monitor_id = monitor_id;
-    win->conn = conn;
-    win->display_channel = channel;
-
-    /* toplevel */
-    win->toplevel = gtk_window_new(GTK_WINDOW_TOPLEVEL);
-    if (spicy_title == NULL) {
-        snprintf(title, sizeof(title), "spice display %d:%d", id, monitor_id);
-    } else {
-        snprintf(title, sizeof(title), "%s", spicy_title);
-    }
-
-    gtk_window_set_title(GTK_WINDOW(win->toplevel), title);
-    g_signal_connect(G_OBJECT(win->toplevel), "window-state-event",
-                     G_CALLBACK(window_state_cb), win);
-    g_signal_connect(G_OBJECT(win->toplevel), "delete-event",
-                     G_CALLBACK(delete_cb), win);
-
-    /* menu + toolbar */
-    win->ui = gtk_ui_manager_new();
-    win->ag = gtk_action_group_new("MenuActions");
-    gtk_action_group_add_actions(win->ag, entries, G_N_ELEMENTS(entries), win);
-    gtk_action_group_add_toggle_actions(win->ag, tentries,
-                                        G_N_ELEMENTS(tentries), win);
-    gtk_action_group_add_radio_actions(win->ag, compression_entries,
-                                       G_N_ELEMENTS(compression_entries), -1,
-                                       G_CALLBACK(compression_cb), win->display_channel);
-    if (!spice_channel_test_capability(win->display_channel, SPICE_DISPLAY_CAP_PREF_COMPRESSION)) {
-        GtkAction *compression_menu_action = gtk_action_group_get_action(win->ag, "CompressionMenu");
-        gtk_action_set_sensitive(compression_menu_action, FALSE);
-    }
-    gtk_ui_manager_insert_action_group(win->ui, win->ag, 0);
-    gtk_window_add_accel_group(GTK_WINDOW(win->toplevel),
-                               gtk_ui_manager_get_accel_group(win->ui));
-
-    err = NULL;
-    if (!gtk_ui_manager_add_ui_from_string(win->ui, ui_xml, -1, &err)) {
-        g_warning("building menus failed: %s", err->message);
-        g_error_free(err);
-        exit(1);
-    }
-    win->menubar = gtk_ui_manager_get_widget(win->ui, "/MainMenu");
-    win->toolbar = gtk_ui_manager_get_widget(win->ui, "/ToolBar");
-
-    /* recent menu */
-    win->ritem  = gtk_ui_manager_get_widget
-        (win->ui, "/MainMenu/FileMenu/FileRecentMenu");
-
-    GtkRecentFilter  *rfilter;
-
-    win->rmenu = gtk_recent_chooser_menu_new();
-    gtk_recent_chooser_set_show_icons(GTK_RECENT_CHOOSER(win->rmenu), FALSE);
-    rfilter = gtk_recent_filter_new();
-    gtk_recent_filter_add_mime_type(rfilter, "application/x-spice");
-    gtk_recent_chooser_add_filter(GTK_RECENT_CHOOSER(win->rmenu), rfilter);
-    gtk_recent_chooser_set_local_only(GTK_RECENT_CHOOSER(win->rmenu), FALSE);
-    gtk_menu_item_set_submenu(GTK_MENU_ITEM(win->ritem), win->rmenu);
-    g_signal_connect(win->rmenu, "item-activated",
-                     G_CALLBACK(recent_item_activated_cb), win);
-
-    /* spice display */
-    win->spice = GTK_WIDGET(spice_display_new_with_monitor(conn->session, id, monitor_id));
-    seq = spice_grab_sequence_new_from_string("Shift_L+F12");
-    spice_display_set_grab_keys(SPICE_DISPLAY(win->spice), seq);
-    spice_grab_sequence_free(seq);
-
-    g_signal_connect(G_OBJECT(win->spice), "mouse-grab",
-                     G_CALLBACK(mouse_grab_cb), win);
-    g_signal_connect(G_OBJECT(win->spice), "keyboard-grab",
-                     G_CALLBACK(keyboard_grab_cb), win);
-    g_signal_connect(G_OBJECT(win->spice), "grab-keys-pressed",
-                     G_CALLBACK(grab_keys_pressed_cb), win);
-
-    /* status line */
-    win->statusbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 1);
-
-    win->status = gtk_label_new("status line");
-    gtk_misc_set_alignment(GTK_MISC(win->status), 0, 0.5);
-    gtk_misc_set_padding(GTK_MISC(win->status), 3, 1);
-    update_status_window(win);
-
-    frame = gtk_frame_new(NULL);
-    gtk_box_pack_start(GTK_BOX(win->statusbar), frame, TRUE, TRUE, 0);
-    gtk_container_add(GTK_CONTAINER(frame), win->status);
-
-    for (i = 0; i < STATE_MAX; i++) {
-        win->st[i] = gtk_label_new("?");
-        gtk_label_set_width_chars(GTK_LABEL(win->st[i]), 5);
-        frame = gtk_frame_new(NULL);
-        gtk_box_pack_end(GTK_BOX(win->statusbar), frame, FALSE, FALSE, 0);
-        gtk_container_add(GTK_CONTAINER(frame), win->st[i]);
-    }
-
-    /* Make a vbox and put stuff in */
-    vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 1);
-    gtk_container_set_border_width(GTK_CONTAINER(vbox), 0);
-    gtk_container_add(GTK_CONTAINER(win->toplevel), vbox);
-    gtk_box_pack_start(GTK_BOX(vbox), win->menubar, FALSE, FALSE, 0);
-    gtk_box_pack_start(GTK_BOX(vbox), win->toolbar, FALSE, FALSE, 0);
-    gtk_box_pack_start(GTK_BOX(vbox), win->spice, TRUE, TRUE, 0);
-    gtk_box_pack_end(GTK_BOX(vbox), win->statusbar, FALSE, TRUE, 0);
-
-    /* show window */
-    if (fullscreen)
-        gtk_window_fullscreen(GTK_WINDOW(win->toplevel));
-
-    gtk_widget_show_all(vbox);
-    restore_configuration(win);
-
-    /* init toggle actions */
-    for (i = 0; i < G_N_ELEMENTS(spice_display_properties); i++) {
-        toggle = gtk_action_group_get_action(win->ag,
-                                             spice_display_properties[i]);
-        g_object_get(win->spice, spice_display_properties[i], &state, NULL);
-        gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(toggle), state);
-    }
-
-    for (i = 0; i < G_N_ELEMENTS(spice_gtk_session_properties); i++) {
-        char notify[64];
-
-        toggle = gtk_action_group_get_action(win->ag,
-                                             spice_gtk_session_properties[i]);
-        g_object_get(win->conn->gtk_session, spice_gtk_session_properties[i],
-                     &state, NULL);
-        gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(toggle), state);
-
-        snprintf(notify, sizeof(notify), "notify::%s",
-                 spice_gtk_session_properties[i]);
-        spice_g_signal_connect_object(win->conn->gtk_session, notify,
-                                      G_CALLBACK(menu_cb_conn_bool_prop_changed),
-                                      win, 0);
-    }
-
-    update_edit_menu_window(win);
-
-    toggle = gtk_action_group_get_action(win->ag, "Toolbar");
-    state = gtk_widget_get_visible(win->toolbar);
-    gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(toggle), state);
-
-    toggle = gtk_action_group_get_action(win->ag, "Statusbar");
-    state = gtk_widget_get_visible(win->statusbar);
-    gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(toggle), state);
-
-#ifdef USE_SMARTCARD
-    gboolean smartcard;
-
-    enable_smartcard_actions(win, NULL, FALSE, FALSE);
-    g_object_get(G_OBJECT(conn->session),
-                 "enable-smartcard", &smartcard,
-                 NULL);
-    if (smartcard) {
-        g_signal_connect(G_OBJECT(spice_smartcard_manager_get()), "reader-added",
-                         (GCallback)reader_added_cb, win);
-        g_signal_connect(G_OBJECT(spice_smartcard_manager_get()), "reader-removed",
-                         (GCallback)reader_removed_cb, win);
-        g_signal_connect(G_OBJECT(spice_smartcard_manager_get()), "card-inserted",
-                         (GCallback)card_inserted_cb, win);
-        g_signal_connect(G_OBJECT(spice_smartcard_manager_get()), "card-removed",
-                         (GCallback)card_removed_cb, win);
-    }
-#endif
-
-#ifndef USE_USBREDIR
-    GtkAction *usbredir = gtk_action_group_get_action(win->ag, "auto-usbredir");
-    gtk_action_set_visible(usbredir, FALSE);
-#endif
-
-    gtk_widget_grab_focus(win->spice);
-
-    return win;
-}
-
-static void destroy_spice_window(SpiceWindow *win)
-{
-    if (win == NULL)
-        return;
-
-    SPICE_DEBUG("destroy window (#%d:%d)", win->id, win->monitor_id);
-    g_object_unref(win->ag);
-    g_object_unref(win->ui);
-    gtk_widget_destroy(win->toplevel);
-    g_object_unref(win);
-}
-
-/* ------------------------------------------------------------------ */
-
-static void recent_add(SpiceSession *session)
-{
-    GtkRecentManager *recent;
-    GtkRecentData meta = {
-        .mime_type    = (char*)"application/x-spice",
-        .app_name     = (char*)"spicy",
-        .app_exec     = (char*)"spicy --uri=%u",
-    };
-    char *uri;
-
-    g_object_get(session, "uri", &uri, NULL);
-    SPICE_DEBUG("%s: %s", __FUNCTION__, uri);
-
-    recent = gtk_recent_manager_get_default();
-    if (g_str_has_prefix(uri, "spice://"))
-        meta.display_name = uri + 8;
-    else if (g_str_has_prefix(uri, "spice+unix://"))
-        meta.display_name = uri + 13;
-    else
-        g_return_if_reached();
-
-    if (!gtk_recent_manager_add_full(recent, uri, &meta))
-        g_warning("Recent item couldn't be added successfully");
-
-    g_free(uri);
-}
-
-static void main_channel_event(SpiceChannel *channel, SpiceChannelEvent event,
-                               gpointer data)
-{
-    const GError *error = NULL;
-    spice_connection *conn = data;
-    char password[64];
-    int rc;
-
-    switch (event) {
-    case SPICE_CHANNEL_OPENED:
-        g_message("main channel: opened");
-        recent_add(conn->session);
-        break;
-    case SPICE_CHANNEL_SWITCHING:
-        g_message("main channel: switching host");
-        break;
-    case SPICE_CHANNEL_CLOSED:
-        /* this event is only sent if the channel was succesfully opened before */
-        g_message("main channel: closed");
-        connection_disconnect(conn);
-        break;
-    case SPICE_CHANNEL_ERROR_IO:
-        connection_disconnect(conn);
-        break;
-    case SPICE_CHANNEL_ERROR_TLS:
-    case SPICE_CHANNEL_ERROR_LINK:
-    case SPICE_CHANNEL_ERROR_CONNECT:
-        error = spice_channel_get_error(channel);
-        g_message("main channel: failed to connect");
-        if (error) {
-            g_message("channel error: %s", error->message);
-        }
-
-        if (spicy_connect_dialog(conn->session)) {
-            connection_connect(conn);
-        } else {
-            connection_disconnect(conn);
-        }
-        break;
-    case SPICE_CHANNEL_ERROR_AUTH:
-        g_warning("main channel: auth failure (wrong password?)");
-        strcpy(password, "");
-        /* FIXME i18 */
-        rc = ask_user(NULL, "Authentication",
-                      "Please enter the spice server password",
-                      password, sizeof(password), true);
-        if (rc == 0) {
-            g_object_set(conn->session, "password", password, NULL);
-            connection_connect(conn);
-        } else {
-            connection_disconnect(conn);
-        }
-        break;
-    default:
-        /* TODO: more sophisticated error handling */
-        g_warning("unknown main channel event: %u", event);
-        /* connection_disconnect(conn); */
-        break;
-    }
-}
-
-static void main_mouse_update(SpiceChannel *channel, gpointer data)
-{
-    spice_connection *conn = data;
-    gint mode;
-
-    g_object_get(channel, "mouse-mode", &mode, NULL);
-    switch (mode) {
-    case SPICE_MOUSE_MODE_SERVER:
-        conn->mouse_state = "server";
-        break;
-    case SPICE_MOUSE_MODE_CLIENT:
-        conn->mouse_state = "client";
-        break;
-    default:
-        conn->mouse_state = "?";
-        break;
-    }
-    update_status(conn);
-}
-
-static void main_agent_update(SpiceChannel *channel, gpointer data)
-{
-    spice_connection *conn = data;
-
-    g_object_get(channel, "agent-connected", &conn->agent_connected, NULL);
-    conn->agent_state = conn->agent_connected ? "yes" : "no";
-    update_status(conn);
-    update_edit_menu(conn);
-}
-
-static void inputs_modifiers(SpiceChannel *channel, gpointer data)
-{
-    spice_connection *conn = data;
-    int m, i;
-
-    g_object_get(channel, "key-modifiers", &m, NULL);
-    for (i = 0; i < SPICE_N_ELEMENTS(conn->wins); i++) {
-        if (conn->wins[i] == NULL)
-            continue;
-
-        gtk_label_set_text(GTK_LABEL(conn->wins[i]->st[STATE_SCROLL_LOCK]),
-                           m & SPICE_KEYBOARD_MODIFIER_FLAGS_SCROLL_LOCK ? "SCROLL" : "");
-        gtk_label_set_text(GTK_LABEL(conn->wins[i]->st[STATE_CAPS_LOCK]),
-                           m & SPICE_KEYBOARD_MODIFIER_FLAGS_CAPS_LOCK ? "CAPS" : "");
-        gtk_label_set_text(GTK_LABEL(conn->wins[i]->st[STATE_NUM_LOCK]),
-                           m & SPICE_KEYBOARD_MODIFIER_FLAGS_NUM_LOCK ? "NUM" : "");
-    }
-}
-
-static void display_mark(SpiceChannel *channel, gint mark, SpiceWindow *win)
-{
-    g_return_if_fail(win != NULL);
-    g_return_if_fail(win->toplevel != NULL);
-
-    if (mark == TRUE) {
-        gtk_widget_show(win->toplevel);
-    } else {
-        gtk_widget_hide(win->toplevel);
-    }
-}
-
-static void update_auto_usbredir_sensitive(spice_connection *conn)
-{
-#ifdef USE_USBREDIR
-    int i;
-    GtkAction *ac;
-    gboolean sensitive;
-
-    sensitive = spice_session_has_channel_type(conn->session,
-                                               SPICE_CHANNEL_USBREDIR);
-    for (i = 0; i < SPICE_N_ELEMENTS(conn->wins); i++) {
-        if (conn->wins[i] == NULL)
-            continue;
-        ac = gtk_action_group_get_action(conn->wins[i]->ag, "auto-usbredir");
-        gtk_action_set_sensitive(ac, sensitive);
-    }
-#endif
-}
-
-static SpiceWindow* get_window(spice_connection *conn, int channel_id, int monitor_id)
-{
-    g_return_val_if_fail(channel_id < CHANNELID_MAX, NULL);
-    g_return_val_if_fail(monitor_id < MONITORID_MAX, NULL);
-
-    return conn->wins[channel_id * CHANNELID_MAX + monitor_id];
-}
-
-static void add_window(spice_connection *conn, SpiceWindow *win)
-{
-    g_return_if_fail(win != NULL);
-    g_return_if_fail(win->id < CHANNELID_MAX);
-    g_return_if_fail(win->monitor_id < MONITORID_MAX);
-    g_return_if_fail(conn->wins[win->id * CHANNELID_MAX + win->monitor_id] == NULL);
-
-    SPICE_DEBUG("add display monitor %d:%d", win->id, win->monitor_id);
-    conn->wins[win->id * CHANNELID_MAX + win->monitor_id] = win;
-}
-
-static void del_window(spice_connection *conn, SpiceWindow *win)
-{
-    if (win == NULL)
-        return;
-
-    g_return_if_fail(win->id < CHANNELID_MAX);
-    g_return_if_fail(win->monitor_id < MONITORID_MAX);
-
-    g_debug("del display monitor %d:%d", win->id, win->monitor_id);
-    conn->wins[win->id * CHANNELID_MAX + win->monitor_id] = NULL;
-    if (win->id > 0)
-        spice_main_set_display_enabled(conn->main, win->id, FALSE);
-    else
-        spice_main_set_display_enabled(conn->main, win->monitor_id, FALSE);
-    spice_main_send_monitor_config(conn->main);
-
-    destroy_spice_window(win);
-}
-
-static void display_monitors(SpiceChannel *display, GParamSpec *pspec,
-                             spice_connection *conn)
-{
-    GArray *monitors = NULL;
-    int id;
-    guint i;
-
-    g_object_get(display,
-                 "channel-id", &id,
-                 "monitors", &monitors,
-                 NULL);
-    g_return_if_fail(monitors != NULL);
-
-    for (i = 0; i < monitors->len; i++) {
-        SpiceWindow *w;
-
-        if (!get_window(conn, id, i)) {
-            w = create_spice_window(conn, display, id, i);
-            add_window(conn, w);
-            spice_g_signal_connect_object(display, "display-mark",
-                                          G_CALLBACK(display_mark), w, 0);
-            gtk_widget_show(w->toplevel);
-            update_auto_usbredir_sensitive(conn);
-        }
-    }
-
-    for (; i < MONITORID_MAX; i++)
-        del_window(conn, get_window(conn, id, i));
-
-    g_clear_pointer(&monitors, g_array_unref);
-}
-
-static void port_write_cb(GObject *source_object,
-                          GAsyncResult *res,
-                          gpointer user_data)
-{
-    SpicePortChannel *port = SPICE_PORT_CHANNEL(source_object);
-    GError *error = NULL;
-
-    spice_port_write_finish(port, res, &error);
-    if (error != NULL)
-        g_warning("%s", error->message);
-    g_clear_error(&error);
-}
-
-static void port_flushed_cb(GObject *source_object,
-                            GAsyncResult *res,
-                            gpointer user_data)
-{
-    SpiceChannel *channel = SPICE_CHANNEL(source_object);
-    GError *error = NULL;
-
-    spice_channel_flush_finish(channel, res, &error);
-    if (error != NULL)
-        g_warning("%s", error->message);
-    g_clear_error(&error);
-
-    spice_channel_disconnect(channel, SPICE_CHANNEL_CLOSED);
-}
-
-static gboolean input_cb(GIOChannel *gin, GIOCondition condition, gpointer data)
-{
-    char buf[4096];
-    gsize bytes_read;
-    GIOStatus status;
-
-    if (!(condition & G_IO_IN))
-        return FALSE;
-
-    status = g_io_channel_read_chars(gin, buf, sizeof(buf), &bytes_read, NULL);
-    if (status != G_IO_STATUS_NORMAL)
-        return FALSE;
-
-    if (stdin_port != NULL)
-        spice_port_write_async(stdin_port, buf, bytes_read, NULL, port_write_cb, NULL);
-
-    return TRUE;
-}
-
-static void watch_stdin(void);
-
-static void port_opened(SpiceChannel *channel, GParamSpec *pspec,
-                        spice_connection *conn)
-{
-    SpicePortChannel *port = SPICE_PORT_CHANNEL(channel);
-    gchar *name = NULL;
-    gboolean opened = FALSE;
-
-    g_object_get(channel,
-                 "port-name", &name,
-                 "port-opened", &opened,
-                 NULL);
-
-    g_printerr("port %p %s: %s\n", channel, name, opened ? "opened" : "closed");
-
-    if (opened) {
-        /* only send a break event and disconnect */
-        if (g_strcmp0(name, "org.spice.spicy.break") == 0) {
-            spice_port_event(port, SPICE_PORT_EVENT_BREAK);
-            spice_channel_flush_async(channel, NULL, port_flushed_cb, conn);
-        }
-
-        /* handle the first spicy port and connect it to stdin/out */
-        if (g_strcmp0(name, "org.spice.spicy") == 0 && stdin_port == NULL) {
-            watch_stdin();
-            stdin_port = port;
-        }
-    } else {
-        if (port == stdin_port)
-            stdin_port = NULL;
-    }
-
-    g_free(name);
-}
-
-static void port_data(SpicePortChannel *port,
-                      gpointer data, int size, spice_connection *conn)
-{
-    int r;
-
-    if (port != stdin_port)
-        return;
-
-    r = write(fileno(stdout), data, size);
-    if (r != size) {
-        g_warning("port write failed result %d/%d errno %d", r, size, errno);
-    }
-}
-
-typedef struct {
-    GtkWidget *vbox;
-    GtkWidget *hbox;
-    GtkWidget *progress;
-    GtkWidget *label;
-    GtkWidget *cancel;
-} TransferTaskWidgets;
-
-static void transfer_update_progress(GObject *object,
-                                     GParamSpec *pspec,
-                                     gpointer user_data)
-{
-    spice_connection *conn = user_data;
-    TransferTaskWidgets *widgets = g_hash_table_lookup(conn->transfers, object);
-    g_return_if_fail(widgets);
-    gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(widgets->progress),
-                                  spice_file_transfer_task_get_progress(SPICE_FILE_TRANSFER_TASK(object)));
-}
-
-static void transfer_task_finished(SpiceFileTransferTask *task, GError *error, spice_connection *conn)
-{
-    if (error)
-        g_warning("%s", error->message);
-    g_hash_table_remove(conn->transfers, task);
-    if (!g_hash_table_size(conn->transfers))
-        gtk_widget_hide(conn->transfer_dialog);
-}
-
-static void dialog_response_cb(GtkDialog *dialog,
-                               gint response_id,
-                               gpointer user_data)
-{
-    spice_connection *conn = user_data;
-    g_print("Reponse: %i\n", response_id);
-
-    if (response_id == GTK_RESPONSE_CANCEL) {
-        GHashTableIter iter;
-        gpointer key, value;
-
-        g_hash_table_iter_init(&iter, conn->transfers);
-        while (g_hash_table_iter_next(&iter, &key, &value)) {
-            SpiceFileTransferTask *task = key;
-            spice_file_transfer_task_cancel(task);
-        }
-    }
-}
-
-static void
-task_cancel_cb(GtkButton *button,
-               gpointer user_data)
-{
-    SpiceFileTransferTask *task = SPICE_FILE_TRANSFER_TASK(user_data);
-    spice_file_transfer_task_cancel(task);
-}
-
-static TransferTaskWidgets *
-transfer_task_widgets_new(SpiceFileTransferTask *task)
-{
-    char *filename;
-    TransferTaskWidgets *widgets = g_new0(TransferTaskWidgets, 1);
-
-    widgets->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
-    widgets->hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
-    widgets->cancel = gtk_button_new_with_label("Cancel");
-
-    widgets->progress = gtk_progress_bar_new();
-    filename = spice_file_transfer_task_get_filename(task);
-    widgets->label = gtk_label_new(filename);
-    g_free(filename);
-
-    gtk_widget_set_halign(widgets->label, GTK_ALIGN_START);
-    gtk_widget_set_valign(widgets->label, GTK_ALIGN_BASELINE);
-    gtk_widget_set_valign(widgets->progress, GTK_ALIGN_CENTER);
-    gtk_widget_set_hexpand(widgets->progress, TRUE);
-    gtk_widget_set_valign(widgets->cancel, GTK_ALIGN_CENTER);
-    gtk_widget_set_hexpand(widgets->progress, FALSE);
-
-    gtk_box_pack_start(GTK_BOX(widgets->hbox), widgets->progress,
-                       TRUE, TRUE, 0);
-    gtk_box_pack_start(GTK_BOX(widgets->hbox), widgets->cancel,
-                       FALSE, TRUE, 0);
-
-    gtk_box_pack_start(GTK_BOX(widgets->vbox), widgets->label,
-                       TRUE, TRUE, 0);
-    gtk_box_pack_start(GTK_BOX(widgets->vbox), widgets->hbox,
-                       TRUE, TRUE, 0);
-
-    g_signal_connect(widgets->cancel, "clicked",
-                     G_CALLBACK(task_cancel_cb), task);
-
-    gtk_widget_show_all(widgets->vbox);
-
-    return widgets;
-}
-
-static void
-transfer_task_widgets_free(TransferTaskWidgets *widgets)
-{
-    /* child widgets will be destroyed automatically */
-    gtk_widget_destroy(widgets->vbox);
-    g_free(widgets);
-}
-
-static void spice_connection_add_task(spice_connection *conn, SpiceFileTransferTask *task)
-{
-    TransferTaskWidgets *widgets;
-    GtkWidget *content = NULL;
-
-    g_signal_connect(task, "notify::progress",
-                     G_CALLBACK(transfer_update_progress), conn);
-    g_signal_connect(task, "finished",
-                     G_CALLBACK(transfer_task_finished), conn);
-    if (!conn->transfer_dialog) {
-        conn->transfer_dialog = gtk_dialog_new_with_buttons("File Transfers",
-                                                            GTK_WINDOW(conn->wins[0]->toplevel), 0,
-                                                            "Cancel", GTK_RESPONSE_CANCEL, NULL);
-        gtk_dialog_set_default_response(GTK_DIALOG(conn->transfer_dialog),
-                                        GTK_RESPONSE_CANCEL);
-        gtk_window_set_resizable(GTK_WINDOW(conn->transfer_dialog), FALSE);
-        g_signal_connect(conn->transfer_dialog, "response",
-                         G_CALLBACK(dialog_response_cb), conn);
-    }
-    gtk_widget_show(conn->transfer_dialog);
-    content = gtk_dialog_get_content_area(GTK_DIALOG(conn->transfer_dialog));
-    gtk_container_set_border_width(GTK_CONTAINER(content), 12);
-
-    widgets = transfer_task_widgets_new(task);
-    g_hash_table_insert(conn->transfers, g_object_ref(task), widgets);
-    gtk_box_pack_start(GTK_BOX(content),
-                       widgets->vbox, TRUE, TRUE, 6);
-}
-
-static void new_file_transfer(SpiceMainChannel *main, SpiceFileTransferTask *task, gpointer user_data)
-{
-    spice_connection *conn = user_data;
-    g_debug("new file transfer task");
-    spice_connection_add_task(conn, task);
-}
-
-static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data)
-{
-    spice_connection *conn = data;
-    int id;
-
-    g_object_get(channel, "channel-id", &id, NULL);
-    conn->channels++;
-    SPICE_DEBUG("new channel (#%d)", id);
-
-    if (SPICE_IS_MAIN_CHANNEL(channel)) {
-        SPICE_DEBUG("new main channel");
-        conn->main = SPICE_MAIN_CHANNEL(channel);
-        g_signal_connect(channel, "channel-event",
-                         G_CALLBACK(main_channel_event), conn);
-        g_signal_connect(channel, "main-mouse-update",
-                         G_CALLBACK(main_mouse_update), conn);
-        g_signal_connect(channel, "main-agent-update",
-                         G_CALLBACK(main_agent_update), conn);
-        g_signal_connect(channel, "new-file-transfer",
-                         G_CALLBACK(new_file_transfer), conn);
-        main_mouse_update(channel, conn);
-        main_agent_update(channel, conn);
-    }
-
-    if (SPICE_IS_DISPLAY_CHANNEL(channel)) {
-        if (id >= SPICE_N_ELEMENTS(conn->wins))
-            return;
-        if (conn->wins[id] != NULL)
-            return;
-        SPICE_DEBUG("new display channel (#%d)", id);
-        g_signal_connect(channel, "notify::monitors",
-                         G_CALLBACK(display_monitors), conn);
-        spice_channel_connect(channel);
-    }
-
-    if (SPICE_IS_INPUTS_CHANNEL(channel)) {
-        SPICE_DEBUG("new inputs channel");
-        g_signal_connect(channel, "inputs-modifiers",
-                         G_CALLBACK(inputs_modifiers), conn);
-    }
-
-    if (SPICE_IS_PLAYBACK_CHANNEL(channel)) {
-        SPICE_DEBUG("new audio channel");
-        conn->audio = spice_audio_get(s, NULL);
-    }
-
-    if (SPICE_IS_USBREDIR_CHANNEL(channel)) {
-        update_auto_usbredir_sensitive(conn);
-    }
-
-    if (SPICE_IS_PORT_CHANNEL(channel)) {
-        g_signal_connect(channel, "notify::port-opened",
-                         G_CALLBACK(port_opened), conn);
-        g_signal_connect(channel, "port-data",
-                         G_CALLBACK(port_data), conn);
-        spice_channel_connect(channel);
-    }
-}
-
-static void channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer data)
-{
-    spice_connection *conn = data;
-    int id;
-
-    g_object_get(channel, "channel-id", &id, NULL);
-    if (SPICE_IS_MAIN_CHANNEL(channel)) {
-        SPICE_DEBUG("zap main channel");
-        conn->main = NULL;
-    }
-
-    if (SPICE_IS_DISPLAY_CHANNEL(channel)) {
-        if (id >= SPICE_N_ELEMENTS(conn->wins))
-            return;
-        SPICE_DEBUG("zap display channel (#%d)", id);
-        /* FIXME destroy widget only */
-    }
-
-    if (SPICE_IS_PLAYBACK_CHANNEL(channel)) {
-        SPICE_DEBUG("zap audio channel");
-    }
-
-    if (SPICE_IS_USBREDIR_CHANNEL(channel)) {
-        update_auto_usbredir_sensitive(conn);
-    }
-
-    if (SPICE_IS_PORT_CHANNEL(channel)) {
-        if (SPICE_PORT_CHANNEL(channel) == stdin_port)
-            stdin_port = NULL;
-    }
-
-    conn->channels--;
-    if (conn->channels > 0) {
-        return;
-    }
-
-    connection_destroy(conn);
-}
-
-static void migration_state(GObject *session,
-                            GParamSpec *pspec, gpointer data)
-{
-    SpiceSessionMigration mig;
-
-    g_object_get(session, "migration-state", &mig, NULL);
-    if (mig == SPICE_SESSION_MIGRATION_SWITCHING)
-        g_message("migrating session");
-}
-
-static spice_connection *connection_new(void)
-{
-    spice_connection *conn;
-    SpiceUsbDeviceManager *manager;
-
-    conn = g_new0(spice_connection, 1);
-    conn->session = spice_session_new();
-    conn->gtk_session = spice_gtk_session_get(conn->session);
-    g_signal_connect(conn->session, "channel-new",
-                     G_CALLBACK(channel_new), conn);
-    g_signal_connect(conn->session, "channel-destroy",
-                     G_CALLBACK(channel_destroy), conn);
-    g_signal_connect(conn->session, "notify::migration-state",
-                     G_CALLBACK(migration_state), conn);
-
-    manager = spice_usb_device_manager_get(conn->session, NULL);
-    if (manager) {
-        g_signal_connect(manager, "auto-connect-failed",
-                         G_CALLBACK(usb_connect_failed), NULL);
-        g_signal_connect(manager, "device-error",
-                         G_CALLBACK(usb_connect_failed), NULL);
-    }
-
-    conn->transfers = g_hash_table_new_full(g_direct_hash, g_direct_equal,
-                                            g_object_unref,
-                                            (GDestroyNotify)transfer_task_widgets_free);
-    connections++;
-    SPICE_DEBUG("%s (%d)", __FUNCTION__, connections);
-    return conn;
-}
-
-static void connection_connect(spice_connection *conn)
-{
-    conn->disconnecting = false;
-    spice_session_connect(conn->session);
-}
-
-static void connection_disconnect(spice_connection *conn)
-{
-    if (conn->disconnecting)
-        return;
-    conn->disconnecting = true;
-    spice_session_disconnect(conn->session);
-}
-
-static void connection_destroy(spice_connection *conn)
-{
-    g_object_unref(conn->session);
-    g_hash_table_unref(conn->transfers);
-    free(conn);
-
-    connections--;
-    SPICE_DEBUG("%s (%d)", __FUNCTION__, connections);
-    if (connections > 0) {
-        return;
-    }
-
-    g_main_loop_quit(mainloop);
-}
-
-/* ------------------------------------------------------------------ */
-
-static GOptionEntry cmd_entries[] = {
-    {
-        .long_name        = "full-screen",
-        .short_name       = 'f',
-        .arg              = G_OPTION_ARG_NONE,
-        .arg_data         = &fullscreen,
-        .description      = "Open in full screen mode",
-    },{
-        .long_name        = "version",
-        .arg              = G_OPTION_ARG_NONE,
-        .arg_data         = &version,
-        .description      = "Display version and quit",
-    },{
-        .long_name        = "title",
-        .arg              = G_OPTION_ARG_STRING,
-        .arg_data         = &spicy_title,
-        .description      = "Set the window title",
-        .arg_description  = "<title>",
-    },{
-        /* end of list */
-    }
-};
-
-static void usb_connect_failed(GObject               *object,
-                               SpiceUsbDevice        *device,
-                               GError                *error,
-                               gpointer               data)
-{
-    GtkWidget *dialog;
-
-    if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED)
-        return;
-
-    dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR,
-                                    GTK_BUTTONS_CLOSE,
-                                    "USB redirection error");
-    gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
-                                             "%s", error->message);
-    gtk_dialog_run(GTK_DIALOG(dialog));
-    gtk_widget_destroy(dialog);
-}
-
-static void setup_terminal(gboolean reset)
-{
-    int stdinfd = fileno(stdin);
-
-    if (!isatty(stdinfd))
-        return;
-
-#ifdef HAVE_TERMIOS_H
-    struct termios tios;
-    static struct termios saved_tios;
-    static bool saved = false;
-
-    if (reset) {
-        if (!saved)
-            return;
-        tios = saved_tios;
-    } else {
-        tcgetattr(stdinfd, &tios);
-        saved_tios = tios;
-        saved = true;
-        tios.c_lflag &= ~(ICANON | ECHO);
-    }
-
-    tcsetattr(stdinfd, TCSANOW, &tios);
-#endif
-}
-
-static void watch_stdin(void)
-{
-    int stdinfd = fileno(stdin);
-    GIOChannel *gin;
-
-    setup_terminal(false);
-    gin = g_io_channel_unix_new(stdinfd);
-    g_io_channel_set_flags(gin, G_IO_FLAG_NONBLOCK, NULL);
-    g_io_add_watch(gin, G_IO_IN|G_IO_ERR|G_IO_HUP|G_IO_NVAL, input_cb, NULL);
-}
-
-int main(int argc, char *argv[])
-{
-    GError *error = NULL;
-    GOptionContext *context;
-    spice_connection *conn;
-    gchar *conf_file, *conf;
-    char *host = NULL, *port = NULL, *tls_port = NULL, *unix_path = NULL;
-
-    keyfile = g_key_file_new();
-
-    int mode = S_IRWXU;
-    conf_file = g_build_filename(g_get_user_config_dir(), "spicy", NULL);
-    if (g_mkdir_with_parents(conf_file, mode) == -1)
-        SPICE_DEBUG("failed to create config directory");
-    g_free(conf_file);
-
-    conf_file = g_build_filename(g_get_user_config_dir(), "spicy", "settings", NULL);
-    if (!g_key_file_load_from_file(keyfile, conf_file,
-                                   G_KEY_FILE_KEEP_COMMENTS|G_KEY_FILE_KEEP_TRANSLATIONS, &error)) {
-        SPICE_DEBUG("Couldn't load configuration: %s", error->message);
-        g_clear_error(&error);
-    }
-
-    /* parse opts */
-    gtk_init(&argc, &argv);
-    context = g_option_context_new("- spice client test application");
-    g_option_context_set_summary(context, "Gtk+ test client to connect to Spice servers.");
-    g_option_context_set_description(context, "Report bugs to " PACKAGE_BUGREPORT ".");
-    g_option_context_add_group(context, spice_get_option_group());
-    g_option_context_set_main_group(context, spice_cmdline_get_option_group());
-    g_option_context_add_main_entries(context, cmd_entries, NULL);
-    g_option_context_add_group(context, gtk_get_option_group(TRUE));
-    if (!g_option_context_parse (context, &argc, &argv, &error)) {
-        g_print("option parsing failed: %s\n", error->message);
-        exit(1);
-    }
-    g_option_context_free(context);
-
-    if (version) {
-        g_print("spicy " PACKAGE_VERSION "\n");
-        exit(0);
-    }
-
-    mainloop = g_main_loop_new(NULL, false);
-
-    conn = connection_new();
-    spice_set_session_option(conn->session);
-    spice_cmdline_session_setup(conn->session);
-
-    g_object_get(conn->session,
-                 "unix-path", &unix_path,
-                 "host", &host,
-                 "port", &port,
-                 "tls-port", &tls_port,
-                 NULL);
-    /* If user doesn't provide hostname and port, show the dialog window
-       instead of connecting to server automatically */
-    if ((host == NULL || (port == NULL && tls_port == NULL)) && unix_path == NULL) {
-        if (!spicy_connect_dialog(conn->session)) {
-            exit(0);
-        }
-    }
-    g_free(host);
-    g_free(port);
-    g_free(tls_port);
-    g_free(unix_path);
-
-    connection_connect(conn);
-    if (connections > 0)
-        g_main_loop_run(mainloop);
-    g_main_loop_unref(mainloop);
-
-    if ((conf = g_key_file_to_data(keyfile, NULL, &error)) == NULL ||
-        !g_file_set_contents(conf_file, conf, -1, &error)) {
-        SPICE_DEBUG("Couldn't save configuration: %s", error->message);
-        g_error_free(error);
-        error = NULL;
-    }
-
-    g_free(conf_file);
-    g_free(conf);
-    g_key_file_free(keyfile);
-
-    g_free(spicy_title);
-
-    setup_terminal(true);
-    return 0;
-}
diff --git a/tools/Makefile.am b/tools/Makefile.am
new file mode 100644
index 0000000..0bdb3c5
--- /dev/null
+++ b/tools/Makefile.am
@@ -0,0 +1,69 @@
+bin_PROGRAMS = spicy-stats spicy-screenshot
+
+TOOLS_CPPFLAGS =			\
+	-DSPICE_COMPILATION		\
+	-I$(top_builddir)/src		\
+	-I$(top_srcdir)			\
+	-I$(top_srcdir)/src		\
+	$(COMMON_CFLAGS)		\
+	$(GLIB2_CFLAGS)			\
+	$(GIO_CFLAGS)			\
+	$(SMARTCARD_CFLAGS)		\
+	$(SPICE_CFLAGS)			\
+	$(NULL)
+
+if WITH_GTK
+bin_PROGRAMS += spicy
+TOOLS_CPPFLAGS += $(GTK_CFLAGS)
+endif
+
+spicy_SOURCES =				\
+	spicy.c				\
+	spicy-connect.h 		\
+	spicy-connect.c 		\
+	spice-cmdline.h			\
+	spice-cmdline.c			\
+	$(NULL)
+
+spicy_LDADD =				\
+	$(top_builddir)/src/libspice-client-gtk-3.0.la	\
+	$(top_builddir)/src/libspice-client-glib-2.0.la	\
+	$(NULL)
+
+# FIXME: GtkAction and lots of GtkUIManager APIs are deprecated
+spicy_CPPFLAGS =			\
+	$(TOOLS_CPPFLAGS)		\
+	-DSPICE_DISABLE_DEPRECATED	\
+	-Wno-deprecated-declarations	\
+	$(NULL)
+
+spicy_screenshot_SOURCES =		\
+	spicy-screenshot.c		\
+	spice-cmdline.h			\
+	spice-cmdline.c			\
+	$(NULL)
+
+spicy_screenshot_LDADD =		\
+	$(top_builddir)/src/libspice-client-glib-2.0.la	\
+	$(GOBJECT2_LIBS)		\
+	$(NULL)
+
+spicy_screenshot_CPPFLAGS =		\
+	$(TOOLS_CPPFLAGS)		\
+	$(NULL)
+
+spicy_stats_SOURCES =			\
+	spicy-stats.c			\
+	spice-cmdline.h			\
+	spice-cmdline.c			\
+	$(NULL)
+
+spicy_stats_LDADD =			\
+	$(top_builddir)/src/libspice-client-glib-2.0.la	\
+	$(NULL)
+
+spicy_stats_CPPFLAGS =			\
+	$(TOOLS_CPPFLAGS)		\
+	$(NULL)
+
+-include $(top_srcdir)/git.mk
diff --git a/tools/spice-cmdline.c b/tools/spice-cmdline.c
new file mode 100644
index 0000000..4b6f4c2
--- /dev/null
+++ b/tools/spice-cmdline.c
@@ -0,0 +1,98 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   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/>.
+*/
+#include "config.h"
+#include <glib/gi18n-lib.h>
+
+#include "spice-client.h"
+#include "spice-common.h"
+#include "spice-cmdline.h"
+
+static char *host;
+static char *port;
+static char *tls_port;
+static char *password;
+static char *uri;
+
+static GOptionEntry spice_entries[] = {
+    {
+        .long_name        = "uri",
+        .arg              = G_OPTION_ARG_STRING,
+        .arg_data         = &uri,
+        .description      = N_("Spice server uri"),
+        .arg_description  = N_("<uri>"),
+    },{
+        .long_name        = "host",
+        .short_name       = 'h',
+        .arg              = G_OPTION_ARG_STRING,
+        .arg_data         = &host,
+        .description      = N_("Spice server address"),
+        .arg_description  = N_("<host>"),
+    },{
+        .long_name        = "port",
+        .short_name       = 'p',
+        .arg              = G_OPTION_ARG_STRING,
+        .arg_data         = &port,
+        .description      = N_("Spice server port"),
+        .arg_description  = N_("<port>"),
+    },{
+        .long_name        = "secure-port",
+        .short_name       = 's',
+        .arg              = G_OPTION_ARG_STRING,
+        .arg_data         = &tls_port,
+        .description      = N_("Spice server secure port"),
+        .arg_description  = N_("<port>"),
+    },{
+        .long_name        = "password",
+        .short_name       = 'w',
+        .arg              = G_OPTION_ARG_STRING,
+        .arg_data         = &password,
+        .description      = N_("Server password"),
+        .arg_description  = N_("<password>"),
+    },{
+        /* end of list */
+    }
+};
+
+GOptionGroup *spice_cmdline_get_option_group(void)
+{
+    GOptionGroup *grp;
+
+    grp = g_option_group_new("spice",
+                             _("Spice connection options:"),
+                             _("Show Spice options"),
+                             NULL, NULL);
+    g_option_group_add_entries(grp, spice_entries);
+
+    return grp;
+}
+
+void spice_cmdline_session_setup(SpiceSession *session)
+{
+    g_return_if_fail(SPICE_IS_SESSION(session));
+
+    if (uri)
+        g_object_set(session, "uri", uri, NULL);
+    if (host)
+        g_object_set(session, "host", host, NULL);
+    if (port)
+        g_object_set(session, "port", port, NULL);
+    if (tls_port)
+        g_object_set(session, "tls-port", tls_port, NULL);
+    if (password)
+        g_object_set(session, "password", password, NULL);
+}
diff --git a/tools/spice-cmdline.h b/tools/spice-cmdline.h
new file mode 100644
index 0000000..11a8086
--- /dev/null
+++ b/tools/spice-cmdline.h
@@ -0,0 +1,29 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   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/>.
+*/
+
+#ifndef SPICE_CMDLINE_H_
+# define SPICE_CMDLINE_H_
+
+G_BEGIN_DECLS
+
+GOptionGroup *spice_cmdline_get_option_group(void);
+void spice_cmdline_session_setup(SpiceSession *session);
+
+G_END_DECLS
+
+#endif // SPICE_CMDLINE_H_
diff --git a/tools/spicy-connect.c b/tools/spicy-connect.c
new file mode 100644
index 0000000..39555a6
--- /dev/null
+++ b/tools/spicy-connect.c
@@ -0,0 +1,248 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2010-2015 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/>.
+*/
+
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include "spice-common.h"
+#include "spicy-connect.h"
+
+typedef struct
+{
+    gboolean connecting;
+    GMainLoop *loop;
+    SpiceSession *session;
+} ConnectionInfo;
+
+static struct {
+    const char *text;
+    const char *prop;
+    GtkWidget *entry;
+} connect_entries[] = {
+    { .text = "Hostname",   .prop = "host"      },
+    { .text = "Port",       .prop = "port"      },
+    { .text = "TLS Port",   .prop = "tls-port"  },
+};
+
+static gboolean can_connect(void)
+{
+    if ((gtk_entry_get_text_length(GTK_ENTRY(connect_entries[0].entry)) > 0) &&
+        ((gtk_entry_get_text_length(GTK_ENTRY(connect_entries[1].entry)) > 0) ||
+         (gtk_entry_get_text_length(GTK_ENTRY(connect_entries[2].entry)) > 0)))
+        return TRUE;
+
+    return FALSE;
+}
+
+static void set_connection_info(SpiceSession *session)
+{
+    const gchar *txt;
+    int i;
+
+    for (i = 0; i < SPICE_N_ELEMENTS(connect_entries); i++) {
+        txt = gtk_entry_get_text(GTK_ENTRY(connect_entries[i].entry));
+        g_object_set(session, connect_entries[i].prop, txt, NULL);
+    }
+}
+
+static gboolean close_cb(gpointer data)
+{
+    ConnectionInfo *info = data;
+    info->connecting = FALSE;
+    if (g_main_loop_is_running(info->loop))
+        g_main_loop_quit(info->loop);
+
+    return TRUE;
+}
+
+static void entry_changed_cb(GtkEditable* entry, gpointer data)
+{
+    GtkButton *connect_button = data;
+    gtk_widget_set_sensitive(GTK_WIDGET(connect_button), can_connect());
+}
+
+static gboolean entry_focus_in_cb(GtkWidget *widget, GdkEvent *event, gpointer data)
+{
+    GtkRecentChooser *recent = GTK_RECENT_CHOOSER(data);
+    gtk_recent_chooser_unselect_all(recent);
+    return TRUE;
+}
+
+static gboolean key_pressed_cb(GtkWidget *widget, GdkEvent *event, gpointer data)
+{
+    gboolean tst;
+    if (event->type == GDK_KEY_PRESS) {
+        switch (event->key.keyval) {
+            case GDK_KEY_Escape:
+                g_signal_emit_by_name(GTK_WIDGET(data), "delete-event", NULL, &tst);
+                return TRUE;
+            default:
+                return FALSE;
+        }
+    }
+
+    return FALSE;
+}
+
+static void recent_selection_changed_dialog_cb(GtkRecentChooser *chooser, gpointer data)
+{
+    GtkRecentInfo *info;
+    gchar *txt = NULL;
+    const gchar *uri;
+    SpiceSession *session = data;
+    int i;
+
+    info = gtk_recent_chooser_get_current_item(chooser);
+    if (info == NULL)
+        return;
+
+    uri = gtk_recent_info_get_uri(info);
+    g_return_if_fail(uri != NULL);
+
+    g_object_set(session, "uri", uri, NULL);
+
+    for (i = 0; i < SPICE_N_ELEMENTS(connect_entries); i++) {
+        g_object_get(session, connect_entries[i].prop, &txt, NULL);
+        gtk_entry_set_text(GTK_ENTRY(connect_entries[i].entry), txt ? txt : "");
+        g_free(txt);
+    }
+
+    gtk_recent_info_unref(info);
+}
+
+static void connect_cb(gpointer data)
+{
+    ConnectionInfo *info = data;
+    if (can_connect())
+    {
+        info->connecting = TRUE;
+        set_connection_info(info->session);
+        if (g_main_loop_is_running(info->loop))
+            g_main_loop_quit(info->loop);
+    }
+}
+
+gboolean spicy_connect_dialog(SpiceSession *session)
+{
+    GtkWidget *connect_button, *cancel_button, *label;
+    GtkBox *main_box, *recent_box, *button_box;
+    GtkWindow *window;
+    GtkGrid *grid;
+    int i;
+
+    ConnectionInfo info = {
+        FALSE,
+        NULL,
+        session
+    };
+
+    /* Create the widgets */
+    window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
+    gtk_window_set_title(window, "Connect to SPICE");
+    gtk_window_set_resizable(window, FALSE);
+    gtk_container_set_border_width(GTK_CONTAINER(window), 5);
+
+    main_box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
+    gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(main_box));
+
+    grid = GTK_GRID(gtk_grid_new());
+    gtk_box_pack_start(main_box, GTK_WIDGET(grid), FALSE, TRUE, 0);
+    gtk_container_set_border_width(GTK_CONTAINER(grid), 5);
+    gtk_grid_set_row_spacing(grid, 5);
+    gtk_grid_set_column_spacing(grid, 5);
+
+    for (i = 0; i < SPICE_N_ELEMENTS(connect_entries); i++) {
+        gchar *txt;
+        label = gtk_label_new(connect_entries[i].text);
+        gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+        gtk_grid_attach(grid, label, 0, i, 1, 1);
+        connect_entries[i].entry = GTK_WIDGET(gtk_entry_new());
+        gtk_grid_attach(grid, connect_entries[i].entry, 1, i, 1, 1);
+        g_object_get(session, connect_entries[i].prop, &txt, NULL);
+        SPICE_DEBUG("%s: #%i [%s]: \"%s\"",
+                __FUNCTION__, i, connect_entries[i].prop, txt);
+        if (txt) {
+            gtk_entry_set_text(GTK_ENTRY(connect_entries[i].entry), txt);
+            g_free(txt);
+        }
+    }
+
+    recent_box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
+    gtk_box_pack_start(main_box, GTK_WIDGET(recent_box), TRUE, TRUE, 0);
+    gtk_container_set_border_width(GTK_CONTAINER(recent_box), 5);
+
+    label = gtk_label_new("Recent connections:");
+    gtk_box_pack_start(recent_box, label, FALSE, TRUE, 0);
+    gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+
+    button_box = GTK_BOX(gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL));
+    gtk_button_box_set_layout(GTK_BUTTON_BOX(button_box), GTK_BUTTONBOX_END);
+    gtk_box_set_spacing(button_box, 5);
+    gtk_container_set_border_width(GTK_CONTAINER(button_box), 5);
+    connect_button = gtk_button_new_with_label("Connect");
+    cancel_button = gtk_button_new_with_label("Cancel");
+    gtk_box_pack_start(button_box, cancel_button, FALSE, TRUE, 0);
+    gtk_box_pack_start(button_box, connect_button, FALSE, TRUE, 1);
+
+    gtk_box_pack_start(main_box, GTK_WIDGET(button_box), FALSE, TRUE, 0);
+
+    gtk_widget_set_sensitive(GTK_WIDGET(connect_button), can_connect());
+
+    g_signal_connect(window, "key-press-event",
+                     G_CALLBACK(key_pressed_cb), window);
+    g_signal_connect_swapped(window, "delete-event",
+                             G_CALLBACK(close_cb), &info);
+    g_signal_connect_swapped(connect_button, "clicked",
+                             G_CALLBACK(connect_cb), &info);
+    g_signal_connect_swapped(cancel_button, "clicked",
+                             G_CALLBACK(close_cb), &info);
+
+    GtkRecentFilter *rfilter;
+    GtkWidget *recent;
+
+    recent = GTK_WIDGET(gtk_recent_chooser_widget_new());
+    gtk_recent_chooser_set_show_icons(GTK_RECENT_CHOOSER(recent), FALSE);
+    gtk_box_pack_start(recent_box, recent, TRUE, TRUE, 0);
+
+    rfilter = gtk_recent_filter_new();
+    gtk_recent_filter_add_mime_type(rfilter, "application/x-spice");
+    gtk_recent_chooser_set_filter(GTK_RECENT_CHOOSER(recent), rfilter);
+    gtk_recent_chooser_set_local_only(GTK_RECENT_CHOOSER(recent), FALSE);
+    g_signal_connect(recent, "selection-changed",
+                     G_CALLBACK(recent_selection_changed_dialog_cb), session);
+    g_signal_connect_swapped(recent, "item-activated",
+                             G_CALLBACK(connect_cb), &info);
+
+    for (i = 0; i < SPICE_N_ELEMENTS(connect_entries); i++) {
+        g_signal_connect_swapped(connect_entries[i].entry, "activate",
+                                 G_CALLBACK(connect_cb), &info);
+        g_signal_connect(connect_entries[i].entry, "changed",
+                         G_CALLBACK(entry_changed_cb), connect_button);
+        g_signal_connect(connect_entries[i].entry, "focus-in-event",
+                         G_CALLBACK(entry_focus_in_cb), recent);
+    }
+
+    /* show and wait for response */
+    gtk_widget_show_all(GTK_WIDGET(window));
+
+    info.loop = g_main_loop_new(NULL, FALSE);
+    g_main_loop_run(info.loop);
+
+    gtk_widget_destroy(GTK_WIDGET(window));
+
+    return info.connecting;
+}
diff --git a/tools/spicy-connect.h b/tools/spicy-connect.h
new file mode 100644
index 0000000..56b2d80
--- /dev/null
+++ b/tools/spicy-connect.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2010-2015 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/>.
+*/
+
+#ifndef SPICY_CONNECT_H
+#define SPICY_CONNECT_H
+
+#include "spice-widget.h"
+
+gboolean spicy_connect_dialog(SpiceSession *session);
+
+#endif
diff --git a/tools/spicy-screenshot.c b/tools/spicy-screenshot.c
new file mode 100644
index 0000000..68f9335
--- /dev/null
+++ b/tools/spicy-screenshot.c
@@ -0,0 +1,194 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   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/>.
+*/
+#include "config.h"
+
+#include "spice-client.h"
+#include "spice-common.h"
+#include "spice-cmdline.h"
+
+/* config */
+static const char *outf      = "spicy-screenshot.ppm";
+static gboolean version = FALSE;
+
+/* state */
+static SpiceSession  *session;
+static GMainLoop     *mainloop;
+
+enum SpiceSurfaceFmt d_format;
+gint                 d_width, d_height, d_stride;
+gpointer             d_data;
+
+/* ------------------------------------------------------------------ */
+
+static void primary_create(SpiceChannel *channel, gint format,
+                           gint width, gint height, gint stride,
+                           gint shmid, gpointer imgdata, gpointer data)
+{
+    SPICE_DEBUG("%s: %dx%d, format %d", __FUNCTION__, width, height, format);
+    d_format = format;
+    d_width  = width;
+    d_height = height;
+    d_stride = stride;
+    d_data   = imgdata;
+}
+
+static int write_ppm_32(void)
+{
+    FILE *fp;
+    uint8_t *p;
+    int n;
+
+    fp = fopen(outf,"w");
+    if (NULL == fp) {
+	fprintf(stderr, "%s: can't open %s: %s\n", g_get_prgname(), outf, strerror(errno));
+	return -1;
+    }
+    fprintf(fp, "P6\n%d %d\n255\n",
+            d_width, d_height);
+    n = d_width * d_height;
+    p = d_data;
+    while (n > 0) {
+#ifdef WORDS_BIGENDIAN
+        fputc(p[1], fp);
+        fputc(p[2], fp);
+        fputc(p[3], fp);
+#else
+        fputc(p[2], fp);
+        fputc(p[1], fp);
+        fputc(p[0], fp);
+#endif
+        p += 4;
+        n--;
+    }
+    fclose(fp);
+    return 0;
+}
+
+static void invalidate(SpiceChannel *channel,
+                       gint x, gint y, gint w, gint h, gpointer *data)
+{
+    int rc;
+
+    switch (d_format) {
+    case SPICE_SURFACE_FMT_32_xRGB:
+        rc = write_ppm_32();
+        break;
+    default:
+        fprintf(stderr, "unsupported spice surface format %u\n", d_format);
+        rc = -1;
+        break;
+    }
+    if (rc == 0)
+        fprintf(stderr, "wrote screen shot to %s\n", outf);
+    g_main_loop_quit(mainloop);
+}
+
+static void main_channel_event(SpiceChannel *channel, SpiceChannelEvent event,
+                               gpointer data)
+{
+    switch (event) {
+    case SPICE_CHANNEL_OPENED:
+        break;
+    default:
+        g_warning("main channel event: %u", event);
+        g_main_loop_quit(mainloop);
+    }
+}
+
+static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer *data)
+{
+    int id;
+
+    if (SPICE_IS_MAIN_CHANNEL(channel)) {
+        g_signal_connect(channel, "channel-event",
+                         G_CALLBACK(main_channel_event), data);
+        return;
+    }
+
+    if (!SPICE_IS_DISPLAY_CHANNEL(channel))
+        return;
+
+    g_object_get(channel, "channel-id", &id, NULL);
+    if (id != 0)
+        return;
+
+    g_signal_connect(channel, "display-primary-create",
+                     G_CALLBACK(primary_create), NULL);
+    g_signal_connect(channel, "display-invalidate",
+                     G_CALLBACK(invalidate), NULL);
+    spice_channel_connect(channel);
+}
+
+/* ------------------------------------------------------------------ */
+
+static GOptionEntry app_entries[] = {
+    {
+        .long_name        = "out-file",
+        .short_name       = 'o',
+        .arg              = G_OPTION_ARG_FILENAME,
+        .arg_data         = &outf,
+        .description      = "Output file name (default spicy-screenshot.ppm)",
+        .arg_description  = "<filename>",
+    },
+    {
+        .long_name        = "version",
+        .arg              = G_OPTION_ARG_NONE,
+        .arg_data         = &version,
+        .description      = "Display version and quit",
+    },
+    {
+        /* end of list */
+    }
+};
+
+int main(int argc, char *argv[])
+{
+    GError *error = NULL;
+    GOptionContext *context;
+
+    /* parse opts */
+    context = g_option_context_new(" - make screen shots");
+    g_option_context_set_summary(context, "A Spice server client to take screenshots in ppm format.");
+    g_option_context_set_description(context, "Report bugs to " PACKAGE_BUGREPORT ".");
+    g_option_context_set_main_group(context, spice_cmdline_get_option_group());
+    g_option_context_add_main_entries(context, app_entries, NULL);
+    if (!g_option_context_parse (context, &argc, &argv, &error)) {
+        g_print("option parsing failed: %s\n", error->message);
+        exit(1);
+    }
+
+    if (version) {
+        g_print("%s " PACKAGE_VERSION "\n", g_get_prgname());
+        exit(0);
+    }
+
+    mainloop = g_main_loop_new(NULL, false);
+
+    session = spice_session_new();
+    g_signal_connect(session, "channel-new",
+                     G_CALLBACK(channel_new), NULL);
+    spice_cmdline_session_setup(session);
+
+    if (!spice_session_connect(session)) {
+        fprintf(stderr, "spice_session_connect failed\n");
+        exit(1);
+    }
+
+    g_main_loop_run(mainloop);
+    return 0;
+}
diff --git a/tools/spicy-stats.c b/tools/spicy-stats.c
new file mode 100644
index 0000000..8ca4cc1
--- /dev/null
+++ b/tools/spicy-stats.c
@@ -0,0 +1,136 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   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/>.
+*/
+#include "config.h"
+
+#include "spice-client.h"
+#include "spice-common.h"
+#include "spice-cmdline.h"
+
+/* config */
+static gboolean version = FALSE;
+
+/* state */
+static SpiceSession  *session;
+static GMainLoop     *mainloop;
+
+/* ------------------------------------------------------------------ */
+static void main_channel_event(SpiceChannel *channel, SpiceChannelEvent event,
+                               gpointer data)
+{
+    switch (event) {
+    case SPICE_CHANNEL_OPENED:
+        break;
+    default:
+        g_warning("main channel event: %u", event);
+        g_main_loop_quit(mainloop);
+    }
+}
+
+static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer *data)
+{
+    int id;
+
+    if (SPICE_IS_MAIN_CHANNEL(channel)) {
+        SPICE_DEBUG("new main channel");
+        g_signal_connect(channel, "channel-event",
+                         G_CALLBACK(main_channel_event), data);
+    }
+
+    if (SPICE_IS_DISPLAY_CHANNEL(channel)) {
+        g_object_get(channel, "channel-id", &id, NULL);
+        if (id != 0)
+            return;
+    }
+
+    spice_channel_connect(channel);
+}
+
+/* ------------------------------------------------------------------ */
+
+static GOptionEntry app_entries[] = {
+    {
+        .long_name        = "version",
+        .arg              = G_OPTION_ARG_NONE,
+        .arg_data         = &version,
+        .description      = "Display version and quit",
+    },
+    {
+        /* end of list */
+    }
+};
+
+static void
+signal_handler(int signum)
+{
+    g_main_loop_quit(mainloop);
+}
+
+int main(int argc, char *argv[])
+{
+    GError *error = NULL;
+    GOptionContext *context;
+
+    signal(SIGINT, signal_handler);
+
+    /* parse opts */
+    context = g_option_context_new(NULL);
+    g_option_context_set_summary(context, "A Spice client used for testing and measurements.");
+    g_option_context_set_description(context, "Report bugs to " PACKAGE_BUGREPORT ".");
+    g_option_context_set_main_group(context, spice_cmdline_get_option_group());
+    g_option_context_add_main_entries(context, app_entries, NULL);
+    if (!g_option_context_parse (context, &argc, &argv, &error)) {
+        g_print("option parsing failed: %s\n", error->message);
+        exit(1);
+    }
+
+    if (version) {
+        g_print("spicy-stats " PACKAGE_VERSION "\n");
+        exit(0);
+    }
+
+    mainloop = g_main_loop_new(NULL, false);
+
+    session = spice_session_new();
+    g_signal_connect(session, "channel-new",
+                     G_CALLBACK(channel_new), NULL);
+    spice_cmdline_session_setup(session);
+
+    if (!spice_session_connect(session)) {
+        fprintf(stderr, "spice_session_connect failed\n");
+        exit(1);
+    }
+
+    g_main_loop_run(mainloop);
+    {
+        GList *iter, *list = spice_session_get_channels(session);
+        gulong total_read_bytes;
+        gint  channel_type;
+        printf("total bytes read:\n");
+        for (iter = list ; iter ; iter = iter->next) {
+            g_object_get(iter->data,
+                "total-read-bytes", &total_read_bytes,
+                "channel-type", &channel_type,
+                NULL);
+            printf("%s: %lu\n",
+                   spice_channel_type_to_string(channel_type),
+                   total_read_bytes);
+        }
+        g_list_free(list);
+    }
+    return 0;
+}
diff --git a/tools/spicy.c b/tools/spicy.c
new file mode 100644
index 0000000..c502428
--- /dev/null
+++ b/tools/spicy.c
@@ -0,0 +1,1938 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2010-2011 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/>.
+*/
+
+#include "config.h"
+#include <glib.h>
+
+#include <sys/stat.h>
+#ifdef HAVE_TERMIOS_H
+#include <termios.h>
+#endif
+
+#ifdef USE_SMARTCARD_012
+#include <vreader.h>
+#endif
+
+#include "spice-widget.h"
+#include "spice-gtk-session.h"
+#include "spice-audio.h"
+#include "spice-common.h"
+#include "spice-cmdline.h"
+#include "spice-option.h"
+#include "usb-device-widget.h"
+
+#include "spicy-connect.h"
+
+typedef struct spice_connection spice_connection;
+
+enum {
+    STATE_SCROLL_LOCK,
+    STATE_CAPS_LOCK,
+    STATE_NUM_LOCK,
+    STATE_MAX,
+};
+
+#define SPICE_TYPE_WINDOW                  (spice_window_get_type ())
+#define SPICE_WINDOW(obj)                  (G_TYPE_CHECK_INSTANCE_CAST ((obj), SPICE_TYPE_WINDOW, SpiceWindow))
+#define SPICE_IS_WINDOW(obj)               (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SPICE_TYPE_WINDOW))
+#define SPICE_WINDOW_CLASS(klass)          (G_TYPE_CHECK_CLASS_CAST ((klass), SPICE_TYPE_WINDOW, SpiceWindowClass))
+#define SPICE_IS_WINDOW_CLASS(klass)       (G_TYPE_CHECK_CLASS_TYPE ((klass), SPICE_TYPE_WINDOW))
+#define SPICE_WINDOW_GET_CLASS(obj)        (G_TYPE_INSTANCE_GET_CLASS ((obj), SPICE_TYPE_WINDOW, SpiceWindowClass))
+
+typedef struct _SpiceWindow SpiceWindow;
+typedef struct _SpiceWindowClass SpiceWindowClass;
+
+struct _SpiceWindow {
+    GObject          object;
+    spice_connection *conn;
+    gint             id;
+    gint             monitor_id;
+    GtkWidget        *toplevel, *spice;
+    GtkWidget        *menubar, *toolbar;
+    GtkWidget        *ritem, *rmenu;
+    GtkWidget        *statusbar, *status, *st[STATE_MAX];
+    GtkActionGroup   *ag;
+    GtkUIManager     *ui;
+    bool             fullscreen;
+    bool             mouse_grabbed;
+    SpiceChannel     *display_channel;
+#ifdef G_OS_WIN32
+    gint             win_x;
+    gint             win_y;
+#endif
+    bool             enable_accels_save;
+    bool             enable_mnemonics_save;
+};
+
+struct _SpiceWindowClass
+{
+  GObjectClass parent_class;
+};
+
+static GType spice_window_get_type(void);
+
+G_DEFINE_TYPE (SpiceWindow, spice_window, G_TYPE_OBJECT);
+
+#define CHANNELID_MAX 4
+#define MONITORID_MAX 4
+
+
+// FIXME: turn this into an object, get rid of fixed wins array, use
+// signals to replace the various callback that iterate over wins array
+struct spice_connection {
+    SpiceSession     *session;
+    SpiceGtkSession  *gtk_session;
+    SpiceMainChannel *main;
+    SpiceWindow     *wins[CHANNELID_MAX * MONITORID_MAX];
+    SpiceAudio       *audio;
+    const char       *mouse_state;
+    const char       *agent_state;
+    gboolean         agent_connected;
+    int              channels;
+    int              disconnecting;
+
+    /* key: SpiceFileTransferTask, value: TransferTaskWidgets */
+    GHashTable *transfers;
+    GtkWidget *transfer_dialog;
+};
+
+static spice_connection *connection_new(void);
+static void connection_connect(spice_connection *conn);
+static void connection_disconnect(spice_connection *conn);
+static void connection_destroy(spice_connection *conn);
+static void usb_connect_failed(GObject               *object,
+                               SpiceUsbDevice        *device,
+                               GError                *error,
+                               gpointer               data);
+static gboolean is_gtk_session_property(const gchar *property);
+static void del_window(spice_connection *conn, SpiceWindow *win);
+
+/* options */
+static gboolean fullscreen = false;
+static gboolean version = false;
+static char *spicy_title = NULL;
+/* globals */
+static GMainLoop     *mainloop = NULL;
+static int           connections = 0;
+static GKeyFile      *keyfile = NULL;
+static SpicePortChannel*stdin_port = NULL;
+
+/* ------------------------------------------------------------------ */
+
+static int ask_user(GtkWidget *parent, char *title, char *message,
+                    char *dest, int dlen, int hide)
+{
+    GtkWidget *dialog, *area, *label, *entry;
+    const char *txt;
+    int retval;
+
+    /* Create the widgets */
+    dialog = gtk_dialog_new_with_buttons(title,
+                                         parent ? GTK_WINDOW(parent) : NULL,
+                                         GTK_DIALOG_DESTROY_WITH_PARENT,
+                                         "_OK",
+                                         GTK_RESPONSE_ACCEPT,
+                                         "_Cancel",
+                                         GTK_RESPONSE_REJECT,
+                                         NULL);
+    gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
+    area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+
+    label = gtk_label_new(message);
+    gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+    gtk_box_pack_start(GTK_BOX(area), label, FALSE, FALSE, 5);
+
+    entry = gtk_entry_new();
+    gtk_entry_set_text(GTK_ENTRY(entry), dest);
+    gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
+    if (hide)
+        gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
+    gtk_box_pack_start(GTK_BOX(area), entry, FALSE, FALSE, 5);
+
+    /* show and wait for response */
+    gtk_widget_show_all(dialog);
+    switch (gtk_dialog_run(GTK_DIALOG(dialog))) {
+    case GTK_RESPONSE_ACCEPT:
+        txt = gtk_entry_get_text(GTK_ENTRY(entry));
+        snprintf(dest, dlen, "%s", txt);
+        retval = 0;
+        break;
+    default:
+        retval = -1;
+        break;
+    }
+    gtk_widget_destroy(dialog);
+    return retval;
+}
+
+static void update_status_window(SpiceWindow *win)
+{
+    gchar *status;
+
+    if (win == NULL)
+        return;
+
+    if (win->mouse_grabbed) {
+        SpiceGrabSequence *sequence = spice_display_get_grab_keys(SPICE_DISPLAY(win->spice));
+        gchar *seq = spice_grab_sequence_as_string(sequence);
+        status = g_strdup_printf("Use %s to ungrab mouse.", seq);
+        g_free(seq);
+    } else {
+        status = g_strdup_printf("mouse: %s, agent: %s",
+                 win->conn->mouse_state, win->conn->agent_state);
+    }
+
+    gtk_label_set_text(GTK_LABEL(win->status), status);
+    g_free(status);
+}
+
+static void update_status(struct spice_connection *conn)
+{
+    int i;
+
+    for (i = 0; i < SPICE_N_ELEMENTS(conn->wins); i++) {
+        if (conn->wins[i] == NULL)
+            continue;
+        update_status_window(conn->wins[i]);
+    }
+}
+
+static const char *spice_edit_properties[] = {
+    "CopyToGuest",
+    "PasteFromGuest",
+};
+
+static void update_edit_menu_window(SpiceWindow *win)
+{
+    int i;
+    GtkAction *toggle;
+
+    if (win == NULL) {
+        return;
+    }
+
+    /* Make "CopyToGuest" and "PasteFromGuest" insensitive if spice
+     * agent is not connected */
+    for (i = 0; i < G_N_ELEMENTS(spice_edit_properties); i++) {
+        toggle = gtk_action_group_get_action(win->ag, spice_edit_properties[i]);
+        if (toggle) {
+            gtk_action_set_sensitive(toggle, win->conn->agent_connected);
+        }
+    }
+}
+
+static void update_edit_menu(struct spice_connection *conn)
+{
+    int i;
+
+    for (i = 0; i < SPICE_N_ELEMENTS(conn->wins); i++) {
+        if (conn->wins[i]) {
+            update_edit_menu_window(conn->wins[i]);
+        }
+    }
+}
+
+static void menu_cb_connect(GtkAction *action, void *data)
+{
+    struct spice_connection *conn;
+
+    conn = connection_new();
+    connection_connect(conn);
+}
+
+static void menu_cb_close(GtkAction *action, void *data)
+{
+    SpiceWindow *win = data;
+
+    connection_disconnect(win->conn);
+}
+
+static void menu_cb_copy(GtkAction *action, void *data)
+{
+    SpiceWindow *win = data;
+
+    spice_gtk_session_copy_to_guest(win->conn->gtk_session);
+}
+
+static void menu_cb_paste(GtkAction *action, void *data)
+{
+    SpiceWindow *win = data;
+
+    spice_gtk_session_paste_from_guest(win->conn->gtk_session);
+}
+
+static void window_set_fullscreen(SpiceWindow *win, gboolean fs)
+{
+    if (fs) {
+#ifdef G_OS_WIN32
+        gtk_window_get_position(GTK_WINDOW(win->toplevel), &win->win_x, &win->win_y);
+#endif
+        gtk_window_fullscreen(GTK_WINDOW(win->toplevel));
+    } else {
+        gtk_window_unfullscreen(GTK_WINDOW(win->toplevel));
+#ifdef G_OS_WIN32
+        gtk_window_move(GTK_WINDOW(win->toplevel), win->win_x, win->win_y);
+#endif
+    }
+}
+
+static void menu_cb_fullscreen(GtkAction *action, void *data)
+{
+    SpiceWindow *win = data;
+
+    window_set_fullscreen(win, !win->fullscreen);
+}
+
+#ifdef USE_SMARTCARD
+static void enable_smartcard_actions(SpiceWindow *win, VReader *reader,
+                                     gboolean can_insert, gboolean can_remove)
+{
+    GtkAction *action;
+
+    if ((reader != NULL) && (!spice_smartcard_reader_is_software((SpiceSmartcardReader*)reader)))
+    {
+        /* Having menu actions to insert/remove smartcards only makes sense
+         * for software smartcard readers, don't do anything when the event
+         * we received was for a "real" smartcard reader.
+         */
+        return;
+    }
+    action = gtk_action_group_get_action(win->ag, "InsertSmartcard");
+    g_return_if_fail(action != NULL);
+    gtk_action_set_sensitive(action, can_insert);
+    action = gtk_action_group_get_action(win->ag, "RemoveSmartcard");
+    g_return_if_fail(action != NULL);
+    gtk_action_set_sensitive(action, can_remove);
+}
+
+
+static void reader_added_cb(SpiceSmartcardManager *manager, VReader *reader,
+                            gpointer user_data)
+{
+    enable_smartcard_actions(user_data, reader, TRUE, FALSE);
+}
+
+static void reader_removed_cb(SpiceSmartcardManager *manager, VReader *reader,
+                              gpointer user_data)
+{
+    enable_smartcard_actions(user_data, reader, FALSE, FALSE);
+}
+
+static void card_inserted_cb(SpiceSmartcardManager *manager, VReader *reader,
+                             gpointer user_data)
+{
+    enable_smartcard_actions(user_data, reader, FALSE, TRUE);
+}
+
+static void card_removed_cb(SpiceSmartcardManager *manager, VReader *reader,
+                            gpointer user_data)
+{
+    enable_smartcard_actions(user_data, reader, TRUE, FALSE);
+}
+
+static void menu_cb_insert_smartcard(GtkAction *action, void *data)
+{
+    spice_smartcard_manager_insert_card(spice_smartcard_manager_get());
+}
+
+static void menu_cb_remove_smartcard(GtkAction *action, void *data)
+{
+    spice_smartcard_manager_remove_card(spice_smartcard_manager_get());
+}
+#endif
+
+static void menu_cb_mouse_mode(GtkAction *action, void *data)
+{
+    SpiceWindow *win = data;
+    SpiceMainChannel *cmain = win->conn->main;
+    int mode;
+
+    g_object_get(cmain, "mouse-mode", &mode, NULL);
+    if (mode == SPICE_MOUSE_MODE_CLIENT)
+        mode = SPICE_MOUSE_MODE_SERVER;
+    else
+        mode = SPICE_MOUSE_MODE_CLIENT;
+
+    spice_main_request_mouse_mode(cmain, mode);
+}
+
+#ifdef USE_USBREDIR
+static void remove_cb(GtkContainer *container, GtkWidget *widget, void *data)
+{
+    gtk_window_resize(GTK_WINDOW(data), 1, 1);
+}
+
+static void menu_cb_select_usb_devices(GtkAction *action, void *data)
+{
+    GtkWidget *dialog, *area, *usb_device_widget;
+    SpiceWindow *win = data;
+
+    /* Create the widgets */
+    dialog = gtk_dialog_new_with_buttons(
+                    "Select USB devices for redirection",
+                    GTK_WINDOW(win->toplevel),
+                    GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
+                    "_Close", GTK_RESPONSE_ACCEPT,
+                    NULL);
+    gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
+    gtk_container_set_border_width(GTK_CONTAINER(dialog), 12);
+    gtk_box_set_spacing(GTK_BOX(gtk_bin_get_child(GTK_BIN(dialog))), 12);
+
+    area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+
+    usb_device_widget = spice_usb_device_widget_new(win->conn->session,
+                                                    NULL); /* default format */
+    g_signal_connect(usb_device_widget, "connect-failed",
+                     G_CALLBACK(usb_connect_failed), NULL);
+    gtk_box_pack_start(GTK_BOX(area), usb_device_widget, TRUE, TRUE, 0);
+
+    /* This shrinks the dialog when USB devices are unplugged */
+    g_signal_connect(usb_device_widget, "remove",
+                     G_CALLBACK(remove_cb), dialog);
+
+    /* show and run */
+    gtk_widget_show_all(dialog);
+    gtk_dialog_run(GTK_DIALOG(dialog));
+    gtk_widget_destroy(dialog);
+}
+#endif
+
+static void menu_cb_bool_prop(GtkToggleAction *action, gpointer data)
+{
+    SpiceWindow *win = data;
+    gboolean state = gtk_toggle_action_get_active(action);
+    const char *name;
+    gpointer object;
+
+    name = gtk_action_get_name(GTK_ACTION(action));
+    SPICE_DEBUG("%s: %s = %s", __FUNCTION__, name, state ? "yes" : "no");
+
+    g_key_file_set_boolean(keyfile, "general", name, state);
+
+    if (is_gtk_session_property(name)) {
+        object = win->conn->gtk_session;
+    } else {
+        object = win->spice;
+    }
+    g_object_set(object, name, state, NULL);
+}
+
+static void menu_cb_conn_bool_prop_changed(GObject    *gobject,
+                                           GParamSpec *pspec,
+                                           gpointer    user_data)
+{
+    SpiceWindow *win = user_data;
+    const gchar *property = g_param_spec_get_name(pspec);
+    GtkAction *toggle;
+    gboolean state;
+
+    toggle = gtk_action_group_get_action(win->ag, property);
+    g_object_get(win->conn->gtk_session, property, &state, NULL);
+    gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(toggle), state);
+}
+
+static void menu_cb_toolbar(GtkToggleAction *action, gpointer data)
+{
+    SpiceWindow *win = data;
+    gboolean state = gtk_toggle_action_get_active(action);
+
+    gtk_widget_set_visible(win->toolbar, state);
+    g_key_file_set_boolean(keyfile, "ui", "toolbar", state);
+}
+
+static void menu_cb_statusbar(GtkToggleAction *action, gpointer data)
+{
+    SpiceWindow *win = data;
+    gboolean state = gtk_toggle_action_get_active(action);
+
+    gtk_widget_set_visible(win->statusbar, state);
+    g_key_file_set_boolean(keyfile, "ui", "statusbar", state);
+}
+
+static void menu_cb_about(GtkAction *action, void *data)
+{
+    char *comments = "gtk test client app for the\n"
+        "spice remote desktop protocol";
+    static const char *copyright = "(c) 2010 Red Hat";
+    static const char *website = "http://www.spice-space.org";
+    static const char *authors[] = { "Gerd Hoffmann <kraxel at redhat.com>",
+                               "Marc-André Lureau <marcandre.lureau at redhat.com>",
+                               NULL };
+    SpiceWindow *win = data;
+
+    gtk_show_about_dialog(GTK_WINDOW(win->toplevel),
+                          "authors",         authors,
+                          "comments",        comments,
+                          "copyright",       copyright,
+                          "logo-icon-name",  "help-about",
+                          "website",         website,
+                          "version",         PACKAGE_VERSION,
+                          "license",         "LGPLv2.1",
+                          NULL);
+}
+
+static gboolean delete_cb(GtkWidget *widget, GdkEvent *event, gpointer data)
+{
+    SpiceWindow *win = data;
+
+    if (win->monitor_id == 0)
+        connection_disconnect(win->conn);
+    else
+        del_window(win->conn, win);
+
+    return true;
+}
+
+static gboolean window_state_cb(GtkWidget *widget, GdkEventWindowState *event,
+                                gpointer data)
+{
+    SpiceWindow *win = data;
+    if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) {
+        win->fullscreen = event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN;
+        if (win->fullscreen) {
+            gtk_widget_hide(win->menubar);
+            gtk_widget_hide(win->toolbar);
+            gtk_widget_hide(win->statusbar);
+            gtk_widget_grab_focus(win->spice);
+        } else {
+            gboolean state;
+            GtkAction *toggle;
+
+            gtk_widget_show(win->menubar);
+            toggle = gtk_action_group_get_action(win->ag, "Toolbar");
+            state = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(toggle));
+            gtk_widget_set_visible(win->toolbar, state);
+            toggle = gtk_action_group_get_action(win->ag, "Statusbar");
+            state = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(toggle));
+            gtk_widget_set_visible(win->statusbar, state);
+        }
+    }
+    return TRUE;
+}
+
+static void grab_keys_pressed_cb(GtkWidget *widget, gpointer data)
+{
+    SpiceWindow *win = data;
+
+    /* since mnemonics are disabled, we leave fullscreen when
+       ungrabbing mouse. Perhaps we should have a different handling
+       of fullscreen key, or simply use a UI, like vinagre */
+    window_set_fullscreen(win, FALSE);
+}
+
+static void mouse_grab_cb(GtkWidget *widget, gint grabbed, gpointer data)
+{
+    SpiceWindow *win = data;
+
+    win->mouse_grabbed = grabbed;
+    update_status(win->conn);
+}
+
+static void keyboard_grab_cb(GtkWidget *widget, gint grabbed, gpointer data)
+{
+    SpiceWindow *win = data;
+    GtkSettings *settings = gtk_widget_get_settings (widget);
+
+    if (grabbed) {
+        /* disable mnemonics & accels */
+        g_object_get(settings,
+                     "gtk-enable-accels", &win->enable_accels_save,
+                     "gtk-enable-mnemonics", &win->enable_mnemonics_save,
+                     NULL);
+        g_object_set(settings,
+                     "gtk-enable-accels", FALSE,
+                     "gtk-enable-mnemonics", FALSE,
+                     NULL);
+    } else {
+        g_object_set(settings,
+                     "gtk-enable-accels", win->enable_accels_save,
+                     "gtk-enable-mnemonics", win->enable_mnemonics_save,
+                     NULL);
+    }
+}
+
+static void restore_configuration(SpiceWindow *win)
+{
+    gboolean state;
+    gchar *str;
+    gchar **keys = NULL;
+    gsize nkeys, i;
+    GError *error = NULL;
+    gpointer object;
+
+    keys = g_key_file_get_keys(keyfile, "general", &nkeys, &error);
+    if (error != NULL) {
+        if (error->code != G_KEY_FILE_ERROR_GROUP_NOT_FOUND)
+            g_warning("Failed to read configuration file keys: %s", error->message);
+        g_clear_error(&error);
+        return;
+    }
+
+    if (nkeys > 0)
+        g_return_if_fail(keys != NULL);
+
+    for (i = 0; i < nkeys; ++i) {
+        if (g_str_equal(keys[i], "grab-sequence"))
+            continue;
+        state = g_key_file_get_boolean(keyfile, "general", keys[i], &error);
+        if (error != NULL) {
+            g_clear_error(&error);
+            continue;
+        }
+
+        if (is_gtk_session_property(keys[i])) {
+            object = win->conn->gtk_session;
+        } else {
+            object = win->spice;
+        }
+        g_object_set(object, keys[i], state, NULL);
+    }
+
+    g_strfreev(keys);
+
+    str = g_key_file_get_string(keyfile, "general", "grab-sequence", &error);
+    if (error == NULL) {
+        SpiceGrabSequence *seq = spice_grab_sequence_new_from_string(str);
+        spice_display_set_grab_keys(SPICE_DISPLAY(win->spice), seq);
+        spice_grab_sequence_free(seq);
+        g_free(str);
+    }
+    g_clear_error(&error);
+
+
+    state = g_key_file_get_boolean(keyfile, "ui", "toolbar", &error);
+    if (error == NULL)
+        gtk_widget_set_visible(win->toolbar, state);
+    g_clear_error(&error);
+
+    state = g_key_file_get_boolean(keyfile, "ui", "statusbar", &error);
+    if (error == NULL)
+        gtk_widget_set_visible(win->statusbar, state);
+    g_clear_error(&error);
+}
+
+/* ------------------------------------------------------------------ */
+
+static const GtkActionEntry entries[] = {
+    {
+        .name        = "FileMenu",
+        .label       = "_File",
+    },{
+        .name        = "FileRecentMenu",
+        .label       = "_Recent",
+    },{
+        .name        = "EditMenu",
+        .label       = "_Edit",
+    },{
+        .name        = "ViewMenu",
+        .label       = "_View",
+    },{
+        .name        = "InputMenu",
+        .label       = "_Input",
+    },{
+        .name        = "OptionMenu",
+        .label       = "_Options",
+    },{
+        .name        = "CompressionMenu",
+        .label       = "_Preferred image compression",
+    },{
+        .name        = "HelpMenu",
+        .label       = "_Help",
+    },{
+
+        /* File menu */
+        .name        = "Connect",
+        .stock_id    = "_Connect",
+        .label       = "_Connect ...",
+        .callback    = G_CALLBACK(menu_cb_connect),
+    },{
+        .name        = "Close",
+        .stock_id    = "window-close",
+        .label       = "_Close",
+        .callback    = G_CALLBACK(menu_cb_close),
+        .accelerator = "", /* none (disable default "<control>W") */
+    },{
+
+        /* Edit menu */
+        .name        = "CopyToGuest",
+        .stock_id    = "edit-copy",
+        .label       = "_Copy to guest",
+        .callback    = G_CALLBACK(menu_cb_copy),
+        .accelerator = "", /* none (disable default "<control>C") */
+    },{
+        .name        = "PasteFromGuest",
+        .stock_id    = "edit-paste",
+        .label       = "_Paste from guest",
+        .callback    = G_CALLBACK(menu_cb_paste),
+        .accelerator = "", /* none (disable default "<control>V") */
+    },{
+
+        /* View menu */
+        .name        = "Fullscreen",
+        .stock_id    = "view-fullscreen",
+        .label       = "_Fullscreen",
+        .callback    = G_CALLBACK(menu_cb_fullscreen),
+        .accelerator = "<shift>F11",
+    },{
+#ifdef USE_SMARTCARD
+	.name        = "InsertSmartcard",
+	.label       = "_Insert Smartcard",
+	.callback    = G_CALLBACK(menu_cb_insert_smartcard),
+        .accelerator = "<shift>F8",
+    },{
+	.name        = "RemoveSmartcard",
+	.label       = "_Remove Smartcard",
+	.callback    = G_CALLBACK(menu_cb_remove_smartcard),
+        .accelerator = "<shift>F9",
+    },{
+#endif
+
+#ifdef USE_USBREDIR
+        .name        = "SelectUsbDevices",
+        .label       = "_Select USB Devices for redirection",
+        .callback    = G_CALLBACK(menu_cb_select_usb_devices),
+        .accelerator = "<shift>F10",
+    },{
+#endif
+
+        .name        = "MouseMode",
+        .label       = "Toggle _mouse mode",
+        .callback    = G_CALLBACK(menu_cb_mouse_mode),
+        .accelerator = "<shift>F7",
+
+    },{
+        /* Help menu */
+        .name        = "About",
+        .stock_id    = "help-about",
+        .label       = "_About ...",
+        .callback    = G_CALLBACK(menu_cb_about),
+    }
+};
+
+static const char *spice_display_properties[] = {
+    "grab-keyboard",
+    "grab-mouse",
+    "resize-guest",
+    "scaling",
+    "disable-inputs",
+};
+
+static const char *spice_gtk_session_properties[] = {
+    "auto-clipboard",
+    "auto-usbredir",
+    "sync-modifiers",
+};
+
+static const GtkToggleActionEntry tentries[] = {
+    {
+        .name        = "grab-keyboard",
+        .label       = "Grab keyboard when active and focused",
+        .callback    = G_CALLBACK(menu_cb_bool_prop),
+    },{
+        .name        = "grab-mouse",
+        .label       = "Grab mouse in server mode (no tablet/vdagent)",
+        .callback    = G_CALLBACK(menu_cb_bool_prop),
+    },{
+        .name        = "resize-guest",
+        .label       = "Resize guest to match window size",
+        .callback    = G_CALLBACK(menu_cb_bool_prop),
+    },{
+        .name        = "scaling",
+        .label       = "Scale display",
+        .callback    = G_CALLBACK(menu_cb_bool_prop),
+    },{
+        .name        = "disable-inputs",
+        .label       = "Disable inputs",
+        .callback    = G_CALLBACK(menu_cb_bool_prop),
+    },{
+        .name        = "sync-modifiers",
+        .label       = "Sync modifiers",
+        .callback    = G_CALLBACK(menu_cb_bool_prop),
+    },{
+        .name        = "auto-clipboard",
+        .label       = "Automatic clipboard sharing between host and guest",
+        .callback    = G_CALLBACK(menu_cb_bool_prop),
+    },{
+        .name        = "auto-usbredir",
+        .label       = "Auto redirect newly plugged in USB devices",
+        .callback    = G_CALLBACK(menu_cb_bool_prop),
+    },{
+        .name        = "Statusbar",
+        .label       = "Statusbar",
+        .callback    = G_CALLBACK(menu_cb_statusbar),
+    },{
+        .name        = "Toolbar",
+        .label       = "Toolbar",
+        .callback    = G_CALLBACK(menu_cb_toolbar),
+    }
+};
+
+static const GtkRadioActionEntry compression_entries[] = {
+    {
+        .name  = "auto-glz",
+        .label = "auto-glz",
+        .value = SPICE_IMAGE_COMPRESSION_AUTO_GLZ,
+    },{
+        .name  = "auto-lz",
+        .label = "auto-lz",
+        .value = SPICE_IMAGE_COMPRESSION_AUTO_LZ,
+    },{
+        .name  = "quic",
+        .label = "quic",
+        .value = SPICE_IMAGE_COMPRESSION_QUIC,
+    },{
+        .name  = "glz",
+        .label = "glz",
+        .value = SPICE_IMAGE_COMPRESSION_GLZ,
+    },{
+        .name  = "lz",
+        .label = "lz",
+        .value = SPICE_IMAGE_COMPRESSION_LZ,
+    },{
+#ifdef USE_LZ4
+        .name  = "lz4",
+        .label = "lz4",
+        .value = SPICE_IMAGE_COMPRESSION_LZ4,
+    },{
+#endif
+        .name  = "off",
+        .label = "off",
+        .value = SPICE_IMAGE_COMPRESSION_OFF,
+    }
+};
+
+static char ui_xml[] =
+"<ui>\n"
+"  <menubar action='MainMenu'>\n"
+"    <menu action='FileMenu'>\n"
+"      <menuitem action='Connect'/>\n"
+"      <menu action='FileRecentMenu'/>\n"
+"      <separator/>\n"
+"      <menuitem action='Close'/>\n"
+"    </menu>\n"
+"    <menu action='EditMenu'>\n"
+"      <menuitem action='CopyToGuest'/>\n"
+"      <menuitem action='PasteFromGuest'/>\n"
+"    </menu>\n"
+"    <menu action='ViewMenu'>\n"
+"      <menuitem action='Fullscreen'/>\n"
+"      <menuitem action='Toolbar'/>\n"
+"      <menuitem action='Statusbar'/>\n"
+"    </menu>\n"
+"    <menu action='InputMenu'>\n"
+#ifdef USE_SMARTCARD
+"      <menuitem action='InsertSmartcard'/>\n"
+"      <menuitem action='RemoveSmartcard'/>\n"
+#endif
+#ifdef USE_USBREDIR
+"      <menuitem action='SelectUsbDevices'/>\n"
+#endif
+"    </menu>\n"
+"    <menu action='OptionMenu'>\n"
+"      <menuitem action='grab-keyboard'/>\n"
+"      <menuitem action='grab-mouse'/>\n"
+"      <menuitem action='MouseMode'/>\n"
+"      <menuitem action='resize-guest'/>\n"
+"      <menuitem action='scaling'/>\n"
+"      <menuitem action='disable-inputs'/>\n"
+"      <menuitem action='sync-modifiers'/>\n"
+"      <menuitem action='auto-clipboard'/>\n"
+"      <menuitem action='auto-usbredir'/>\n"
+"      <menu action='CompressionMenu'>\n"
+"        <menuitem action='auto-glz'/>\n"
+"        <menuitem action='auto-lz'/>\n"
+"        <menuitem action='quic'/>\n"
+"        <menuitem action='glz'/>\n"
+"        <menuitem action='lz'/>\n"
+#ifdef USE_LZ4
+"        <menuitem action='lz4'/>\n"
+#endif
+"        <menuitem action='off'/>\n"
+"      </menu>\n"
+"    </menu>\n"
+"    <menu action='HelpMenu'>\n"
+"      <menuitem action='About'/>\n"
+"    </menu>\n"
+"  </menubar>\n"
+"  <toolbar action='ToolBar'>\n"
+"    <toolitem action='Close'/>\n"
+"    <separator/>\n"
+"    <toolitem action='CopyToGuest'/>\n"
+"    <toolitem action='PasteFromGuest'/>\n"
+"    <separator/>\n"
+"    <toolitem action='Fullscreen'/>\n"
+"  </toolbar>\n"
+"</ui>\n";
+
+static gboolean is_gtk_session_property(const gchar *property)
+{
+    int i;
+
+    for (i = 0; i < G_N_ELEMENTS(spice_gtk_session_properties); i++) {
+        if (!strcmp(spice_gtk_session_properties[i], property)) {
+            return TRUE;
+        }
+    }
+    return FALSE;
+}
+
+static void recent_item_activated_cb(GtkRecentChooser *chooser, gpointer data)
+{
+    GtkRecentInfo *info;
+    struct spice_connection *conn;
+    const char *uri;
+
+    info = gtk_recent_chooser_get_current_item(chooser);
+
+    uri = gtk_recent_info_get_uri(info);
+    g_return_if_fail(uri != NULL);
+
+    conn = connection_new();
+    g_object_set(conn->session, "uri", uri, NULL);
+    gtk_recent_info_unref(info);
+    connection_connect(conn);
+}
+
+static void compression_cb(GtkRadioAction *action G_GNUC_UNUSED,
+                           GtkRadioAction *current,
+                           gpointer user_data)
+{
+    spice_display_change_preferred_compression(SPICE_CHANNEL(user_data),
+                                               gtk_radio_action_get_current_value(current));
+}
+
+static void
+spice_window_class_init (SpiceWindowClass *klass)
+{
+}
+
+static void
+spice_window_init (SpiceWindow *self)
+{
+}
+
+static SpiceWindow *create_spice_window(spice_connection *conn, SpiceChannel *channel, int id, gint monitor_id)
+{
+    char title[32];
+    SpiceWindow *win;
+    GtkAction *toggle;
+    gboolean state;
+    GtkWidget *vbox, *frame;
+    GError *err = NULL;
+    int i;
+    SpiceGrabSequence *seq;
+
+    win = g_object_new(SPICE_TYPE_WINDOW, NULL);
+    win->id = id;
+    win->monitor_id = monitor_id;
+    win->conn = conn;
+    win->display_channel = channel;
+
+    /* toplevel */
+    win->toplevel = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+    if (spicy_title == NULL) {
+        snprintf(title, sizeof(title), "spice display %d:%d", id, monitor_id);
+    } else {
+        snprintf(title, sizeof(title), "%s", spicy_title);
+    }
+
+    gtk_window_set_title(GTK_WINDOW(win->toplevel), title);
+    g_signal_connect(G_OBJECT(win->toplevel), "window-state-event",
+                     G_CALLBACK(window_state_cb), win);
+    g_signal_connect(G_OBJECT(win->toplevel), "delete-event",
+                     G_CALLBACK(delete_cb), win);
+
+    /* menu + toolbar */
+    win->ui = gtk_ui_manager_new();
+    win->ag = gtk_action_group_new("MenuActions");
+    gtk_action_group_add_actions(win->ag, entries, G_N_ELEMENTS(entries), win);
+    gtk_action_group_add_toggle_actions(win->ag, tentries,
+                                        G_N_ELEMENTS(tentries), win);
+    gtk_action_group_add_radio_actions(win->ag, compression_entries,
+                                       G_N_ELEMENTS(compression_entries), -1,
+                                       G_CALLBACK(compression_cb), win->display_channel);
+    if (!spice_channel_test_capability(win->display_channel, SPICE_DISPLAY_CAP_PREF_COMPRESSION)) {
+        GtkAction *compression_menu_action = gtk_action_group_get_action(win->ag, "CompressionMenu");
+        gtk_action_set_sensitive(compression_menu_action, FALSE);
+    }
+    gtk_ui_manager_insert_action_group(win->ui, win->ag, 0);
+    gtk_window_add_accel_group(GTK_WINDOW(win->toplevel),
+                               gtk_ui_manager_get_accel_group(win->ui));
+
+    err = NULL;
+    if (!gtk_ui_manager_add_ui_from_string(win->ui, ui_xml, -1, &err)) {
+        g_warning("building menus failed: %s", err->message);
+        g_error_free(err);
+        exit(1);
+    }
+    win->menubar = gtk_ui_manager_get_widget(win->ui, "/MainMenu");
+    win->toolbar = gtk_ui_manager_get_widget(win->ui, "/ToolBar");
+
+    /* recent menu */
+    win->ritem  = gtk_ui_manager_get_widget
+        (win->ui, "/MainMenu/FileMenu/FileRecentMenu");
+
+    GtkRecentFilter  *rfilter;
+
+    win->rmenu = gtk_recent_chooser_menu_new();
+    gtk_recent_chooser_set_show_icons(GTK_RECENT_CHOOSER(win->rmenu), FALSE);
+    rfilter = gtk_recent_filter_new();
+    gtk_recent_filter_add_mime_type(rfilter, "application/x-spice");
+    gtk_recent_chooser_add_filter(GTK_RECENT_CHOOSER(win->rmenu), rfilter);
+    gtk_recent_chooser_set_local_only(GTK_RECENT_CHOOSER(win->rmenu), FALSE);
+    gtk_menu_item_set_submenu(GTK_MENU_ITEM(win->ritem), win->rmenu);
+    g_signal_connect(win->rmenu, "item-activated",
+                     G_CALLBACK(recent_item_activated_cb), win);
+
+    /* spice display */
+    win->spice = GTK_WIDGET(spice_display_new_with_monitor(conn->session, id, monitor_id));
+    seq = spice_grab_sequence_new_from_string("Shift_L+F12");
+    spice_display_set_grab_keys(SPICE_DISPLAY(win->spice), seq);
+    spice_grab_sequence_free(seq);
+
+    g_signal_connect(G_OBJECT(win->spice), "mouse-grab",
+                     G_CALLBACK(mouse_grab_cb), win);
+    g_signal_connect(G_OBJECT(win->spice), "keyboard-grab",
+                     G_CALLBACK(keyboard_grab_cb), win);
+    g_signal_connect(G_OBJECT(win->spice), "grab-keys-pressed",
+                     G_CALLBACK(grab_keys_pressed_cb), win);
+
+    /* status line */
+    win->statusbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 1);
+
+    win->status = gtk_label_new("status line");
+    gtk_misc_set_alignment(GTK_MISC(win->status), 0, 0.5);
+    gtk_misc_set_padding(GTK_MISC(win->status), 3, 1);
+    update_status_window(win);
+
+    frame = gtk_frame_new(NULL);
+    gtk_box_pack_start(GTK_BOX(win->statusbar), frame, TRUE, TRUE, 0);
+    gtk_container_add(GTK_CONTAINER(frame), win->status);
+
+    for (i = 0; i < STATE_MAX; i++) {
+        win->st[i] = gtk_label_new("?");
+        gtk_label_set_width_chars(GTK_LABEL(win->st[i]), 5);
+        frame = gtk_frame_new(NULL);
+        gtk_box_pack_end(GTK_BOX(win->statusbar), frame, FALSE, FALSE, 0);
+        gtk_container_add(GTK_CONTAINER(frame), win->st[i]);
+    }
+
+    /* Make a vbox and put stuff in */
+    vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 1);
+    gtk_container_set_border_width(GTK_CONTAINER(vbox), 0);
+    gtk_container_add(GTK_CONTAINER(win->toplevel), vbox);
+    gtk_box_pack_start(GTK_BOX(vbox), win->menubar, FALSE, FALSE, 0);
+    gtk_box_pack_start(GTK_BOX(vbox), win->toolbar, FALSE, FALSE, 0);
+    gtk_box_pack_start(GTK_BOX(vbox), win->spice, TRUE, TRUE, 0);
+    gtk_box_pack_end(GTK_BOX(vbox), win->statusbar, FALSE, TRUE, 0);
+
+    /* show window */
+    if (fullscreen)
+        gtk_window_fullscreen(GTK_WINDOW(win->toplevel));
+
+    gtk_widget_show_all(vbox);
+    restore_configuration(win);
+
+    /* init toggle actions */
+    for (i = 0; i < G_N_ELEMENTS(spice_display_properties); i++) {
+        toggle = gtk_action_group_get_action(win->ag,
+                                             spice_display_properties[i]);
+        g_object_get(win->spice, spice_display_properties[i], &state, NULL);
+        gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(toggle), state);
+    }
+
+    for (i = 0; i < G_N_ELEMENTS(spice_gtk_session_properties); i++) {
+        char notify[64];
+
+        toggle = gtk_action_group_get_action(win->ag,
+                                             spice_gtk_session_properties[i]);
+        g_object_get(win->conn->gtk_session, spice_gtk_session_properties[i],
+                     &state, NULL);
+        gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(toggle), state);
+
+        snprintf(notify, sizeof(notify), "notify::%s",
+                 spice_gtk_session_properties[i]);
+        spice_g_signal_connect_object(win->conn->gtk_session, notify,
+                                      G_CALLBACK(menu_cb_conn_bool_prop_changed),
+                                      win, 0);
+    }
+
+    update_edit_menu_window(win);
+
+    toggle = gtk_action_group_get_action(win->ag, "Toolbar");
+    state = gtk_widget_get_visible(win->toolbar);
+    gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(toggle), state);
+
+    toggle = gtk_action_group_get_action(win->ag, "Statusbar");
+    state = gtk_widget_get_visible(win->statusbar);
+    gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(toggle), state);
+
+#ifdef USE_SMARTCARD
+    gboolean smartcard;
+
+    enable_smartcard_actions(win, NULL, FALSE, FALSE);
+    g_object_get(G_OBJECT(conn->session),
+                 "enable-smartcard", &smartcard,
+                 NULL);
+    if (smartcard) {
+        g_signal_connect(G_OBJECT(spice_smartcard_manager_get()), "reader-added",
+                         (GCallback)reader_added_cb, win);
+        g_signal_connect(G_OBJECT(spice_smartcard_manager_get()), "reader-removed",
+                         (GCallback)reader_removed_cb, win);
+        g_signal_connect(G_OBJECT(spice_smartcard_manager_get()), "card-inserted",
+                         (GCallback)card_inserted_cb, win);
+        g_signal_connect(G_OBJECT(spice_smartcard_manager_get()), "card-removed",
+                         (GCallback)card_removed_cb, win);
+    }
+#endif
+
+#ifndef USE_USBREDIR
+    GtkAction *usbredir = gtk_action_group_get_action(win->ag, "auto-usbredir");
+    gtk_action_set_visible(usbredir, FALSE);
+#endif
+
+    gtk_widget_grab_focus(win->spice);
+
+    return win;
+}
+
+static void destroy_spice_window(SpiceWindow *win)
+{
+    if (win == NULL)
+        return;
+
+    SPICE_DEBUG("destroy window (#%d:%d)", win->id, win->monitor_id);
+    g_object_unref(win->ag);
+    g_object_unref(win->ui);
+    gtk_widget_destroy(win->toplevel);
+    g_object_unref(win);
+}
+
+/* ------------------------------------------------------------------ */
+
+static void recent_add(SpiceSession *session)
+{
+    GtkRecentManager *recent;
+    GtkRecentData meta = {
+        .mime_type    = (char*)"application/x-spice",
+        .app_name     = (char*)"spicy",
+        .app_exec     = (char*)"spicy --uri=%u",
+    };
+    char *uri;
+
+    g_object_get(session, "uri", &uri, NULL);
+    SPICE_DEBUG("%s: %s", __FUNCTION__, uri);
+
+    recent = gtk_recent_manager_get_default();
+    if (g_str_has_prefix(uri, "spice://"))
+        meta.display_name = uri + 8;
+    else if (g_str_has_prefix(uri, "spice+unix://"))
+        meta.display_name = uri + 13;
+    else
+        g_return_if_reached();
+
+    if (!gtk_recent_manager_add_full(recent, uri, &meta))
+        g_warning("Recent item couldn't be added successfully");
+
+    g_free(uri);
+}
+
+static void main_channel_event(SpiceChannel *channel, SpiceChannelEvent event,
+                               gpointer data)
+{
+    const GError *error = NULL;
+    spice_connection *conn = data;
+    char password[64];
+    int rc;
+
+    switch (event) {
+    case SPICE_CHANNEL_OPENED:
+        g_message("main channel: opened");
+        recent_add(conn->session);
+        break;
+    case SPICE_CHANNEL_SWITCHING:
+        g_message("main channel: switching host");
+        break;
+    case SPICE_CHANNEL_CLOSED:
+        /* this event is only sent if the channel was succesfully opened before */
+        g_message("main channel: closed");
+        connection_disconnect(conn);
+        break;
+    case SPICE_CHANNEL_ERROR_IO:
+        connection_disconnect(conn);
+        break;
+    case SPICE_CHANNEL_ERROR_TLS:
+    case SPICE_CHANNEL_ERROR_LINK:
+    case SPICE_CHANNEL_ERROR_CONNECT:
+        error = spice_channel_get_error(channel);
+        g_message("main channel: failed to connect");
+        if (error) {
+            g_message("channel error: %s", error->message);
+        }
+
+        if (spicy_connect_dialog(conn->session)) {
+            connection_connect(conn);
+        } else {
+            connection_disconnect(conn);
+        }
+        break;
+    case SPICE_CHANNEL_ERROR_AUTH:
+        g_warning("main channel: auth failure (wrong password?)");
+        strcpy(password, "");
+        /* FIXME i18 */
+        rc = ask_user(NULL, "Authentication",
+                      "Please enter the spice server password",
+                      password, sizeof(password), true);
+        if (rc == 0) {
+            g_object_set(conn->session, "password", password, NULL);
+            connection_connect(conn);
+        } else {
+            connection_disconnect(conn);
+        }
+        break;
+    default:
+        /* TODO: more sophisticated error handling */
+        g_warning("unknown main channel event: %u", event);
+        /* connection_disconnect(conn); */
+        break;
+    }
+}
+
+static void main_mouse_update(SpiceChannel *channel, gpointer data)
+{
+    spice_connection *conn = data;
+    gint mode;
+
+    g_object_get(channel, "mouse-mode", &mode, NULL);
+    switch (mode) {
+    case SPICE_MOUSE_MODE_SERVER:
+        conn->mouse_state = "server";
+        break;
+    case SPICE_MOUSE_MODE_CLIENT:
+        conn->mouse_state = "client";
+        break;
+    default:
+        conn->mouse_state = "?";
+        break;
+    }
+    update_status(conn);
+}
+
+static void main_agent_update(SpiceChannel *channel, gpointer data)
+{
+    spice_connection *conn = data;
+
+    g_object_get(channel, "agent-connected", &conn->agent_connected, NULL);
+    conn->agent_state = conn->agent_connected ? "yes" : "no";
+    update_status(conn);
+    update_edit_menu(conn);
+}
+
+static void inputs_modifiers(SpiceChannel *channel, gpointer data)
+{
+    spice_connection *conn = data;
+    int m, i;
+
+    g_object_get(channel, "key-modifiers", &m, NULL);
+    for (i = 0; i < SPICE_N_ELEMENTS(conn->wins); i++) {
+        if (conn->wins[i] == NULL)
+            continue;
+
+        gtk_label_set_text(GTK_LABEL(conn->wins[i]->st[STATE_SCROLL_LOCK]),
+                           m & SPICE_KEYBOARD_MODIFIER_FLAGS_SCROLL_LOCK ? "SCROLL" : "");
+        gtk_label_set_text(GTK_LABEL(conn->wins[i]->st[STATE_CAPS_LOCK]),
+                           m & SPICE_KEYBOARD_MODIFIER_FLAGS_CAPS_LOCK ? "CAPS" : "");
+        gtk_label_set_text(GTK_LABEL(conn->wins[i]->st[STATE_NUM_LOCK]),
+                           m & SPICE_KEYBOARD_MODIFIER_FLAGS_NUM_LOCK ? "NUM" : "");
+    }
+}
+
+static void display_mark(SpiceChannel *channel, gint mark, SpiceWindow *win)
+{
+    g_return_if_fail(win != NULL);
+    g_return_if_fail(win->toplevel != NULL);
+
+    if (mark == TRUE) {
+        gtk_widget_show(win->toplevel);
+    } else {
+        gtk_widget_hide(win->toplevel);
+    }
+}
+
+static void update_auto_usbredir_sensitive(spice_connection *conn)
+{
+#ifdef USE_USBREDIR
+    int i;
+    GtkAction *ac;
+    gboolean sensitive;
+
+    sensitive = spice_session_has_channel_type(conn->session,
+                                               SPICE_CHANNEL_USBREDIR);
+    for (i = 0; i < SPICE_N_ELEMENTS(conn->wins); i++) {
+        if (conn->wins[i] == NULL)
+            continue;
+        ac = gtk_action_group_get_action(conn->wins[i]->ag, "auto-usbredir");
+        gtk_action_set_sensitive(ac, sensitive);
+    }
+#endif
+}
+
+static SpiceWindow* get_window(spice_connection *conn, int channel_id, int monitor_id)
+{
+    g_return_val_if_fail(channel_id < CHANNELID_MAX, NULL);
+    g_return_val_if_fail(monitor_id < MONITORID_MAX, NULL);
+
+    return conn->wins[channel_id * CHANNELID_MAX + monitor_id];
+}
+
+static void add_window(spice_connection *conn, SpiceWindow *win)
+{
+    g_return_if_fail(win != NULL);
+    g_return_if_fail(win->id < CHANNELID_MAX);
+    g_return_if_fail(win->monitor_id < MONITORID_MAX);
+    g_return_if_fail(conn->wins[win->id * CHANNELID_MAX + win->monitor_id] == NULL);
+
+    SPICE_DEBUG("add display monitor %d:%d", win->id, win->monitor_id);
+    conn->wins[win->id * CHANNELID_MAX + win->monitor_id] = win;
+}
+
+static void del_window(spice_connection *conn, SpiceWindow *win)
+{
+    if (win == NULL)
+        return;
+
+    g_return_if_fail(win->id < CHANNELID_MAX);
+    g_return_if_fail(win->monitor_id < MONITORID_MAX);
+
+    g_debug("del display monitor %d:%d", win->id, win->monitor_id);
+    conn->wins[win->id * CHANNELID_MAX + win->monitor_id] = NULL;
+    if (win->id > 0)
+        spice_main_set_display_enabled(conn->main, win->id, FALSE);
+    else
+        spice_main_set_display_enabled(conn->main, win->monitor_id, FALSE);
+    spice_main_send_monitor_config(conn->main);
+
+    destroy_spice_window(win);
+}
+
+static void display_monitors(SpiceChannel *display, GParamSpec *pspec,
+                             spice_connection *conn)
+{
+    GArray *monitors = NULL;
+    int id;
+    guint i;
+
+    g_object_get(display,
+                 "channel-id", &id,
+                 "monitors", &monitors,
+                 NULL);
+    g_return_if_fail(monitors != NULL);
+
+    for (i = 0; i < monitors->len; i++) {
+        SpiceWindow *w;
+
+        if (!get_window(conn, id, i)) {
+            w = create_spice_window(conn, display, id, i);
+            add_window(conn, w);
+            spice_g_signal_connect_object(display, "display-mark",
+                                          G_CALLBACK(display_mark), w, 0);
+            gtk_widget_show(w->toplevel);
+            update_auto_usbredir_sensitive(conn);
+        }
+    }
+
+    for (; i < MONITORID_MAX; i++)
+        del_window(conn, get_window(conn, id, i));
+
+    g_clear_pointer(&monitors, g_array_unref);
+}
+
+static void port_write_cb(GObject *source_object,
+                          GAsyncResult *res,
+                          gpointer user_data)
+{
+    SpicePortChannel *port = SPICE_PORT_CHANNEL(source_object);
+    GError *error = NULL;
+
+    spice_port_write_finish(port, res, &error);
+    if (error != NULL)
+        g_warning("%s", error->message);
+    g_clear_error(&error);
+}
+
+static void port_flushed_cb(GObject *source_object,
+                            GAsyncResult *res,
+                            gpointer user_data)
+{
+    SpiceChannel *channel = SPICE_CHANNEL(source_object);
+    GError *error = NULL;
+
+    spice_channel_flush_finish(channel, res, &error);
+    if (error != NULL)
+        g_warning("%s", error->message);
+    g_clear_error(&error);
+
+    spice_channel_disconnect(channel, SPICE_CHANNEL_CLOSED);
+}
+
+static gboolean input_cb(GIOChannel *gin, GIOCondition condition, gpointer data)
+{
+    char buf[4096];
+    gsize bytes_read;
+    GIOStatus status;
+
+    if (!(condition & G_IO_IN))
+        return FALSE;
+
+    status = g_io_channel_read_chars(gin, buf, sizeof(buf), &bytes_read, NULL);
+    if (status != G_IO_STATUS_NORMAL)
+        return FALSE;
+
+    if (stdin_port != NULL)
+        spice_port_write_async(stdin_port, buf, bytes_read, NULL, port_write_cb, NULL);
+
+    return TRUE;
+}
+
+static void watch_stdin(void);
+
+static void port_opened(SpiceChannel *channel, GParamSpec *pspec,
+                        spice_connection *conn)
+{
+    SpicePortChannel *port = SPICE_PORT_CHANNEL(channel);
+    gchar *name = NULL;
+    gboolean opened = FALSE;
+
+    g_object_get(channel,
+                 "port-name", &name,
+                 "port-opened", &opened,
+                 NULL);
+
+    g_printerr("port %p %s: %s\n", channel, name, opened ? "opened" : "closed");
+
+    if (opened) {
+        /* only send a break event and disconnect */
+        if (g_strcmp0(name, "org.spice.spicy.break") == 0) {
+            spice_port_event(port, SPICE_PORT_EVENT_BREAK);
+            spice_channel_flush_async(channel, NULL, port_flushed_cb, conn);
+        }
+
+        /* handle the first spicy port and connect it to stdin/out */
+        if (g_strcmp0(name, "org.spice.spicy") == 0 && stdin_port == NULL) {
+            watch_stdin();
+            stdin_port = port;
+        }
+    } else {
+        if (port == stdin_port)
+            stdin_port = NULL;
+    }
+
+    g_free(name);
+}
+
+static void port_data(SpicePortChannel *port,
+                      gpointer data, int size, spice_connection *conn)
+{
+    int r;
+
+    if (port != stdin_port)
+        return;
+
+    r = write(fileno(stdout), data, size);
+    if (r != size) {
+        g_warning("port write failed result %d/%d errno %d", r, size, errno);
+    }
+}
+
+typedef struct {
+    GtkWidget *vbox;
+    GtkWidget *hbox;
+    GtkWidget *progress;
+    GtkWidget *label;
+    GtkWidget *cancel;
+} TransferTaskWidgets;
+
+static void transfer_update_progress(GObject *object,
+                                     GParamSpec *pspec,
+                                     gpointer user_data)
+{
+    spice_connection *conn = user_data;
+    TransferTaskWidgets *widgets = g_hash_table_lookup(conn->transfers, object);
+    g_return_if_fail(widgets);
+    gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(widgets->progress),
+                                  spice_file_transfer_task_get_progress(SPICE_FILE_TRANSFER_TASK(object)));
+}
+
+static void transfer_task_finished(SpiceFileTransferTask *task, GError *error, spice_connection *conn)
+{
+    if (error)
+        g_warning("%s", error->message);
+    g_hash_table_remove(conn->transfers, task);
+    if (!g_hash_table_size(conn->transfers))
+        gtk_widget_hide(conn->transfer_dialog);
+}
+
+static void dialog_response_cb(GtkDialog *dialog,
+                               gint response_id,
+                               gpointer user_data)
+{
+    spice_connection *conn = user_data;
+    g_print("Reponse: %i\n", response_id);
+
+    if (response_id == GTK_RESPONSE_CANCEL) {
+        GHashTableIter iter;
+        gpointer key, value;
+
+        g_hash_table_iter_init(&iter, conn->transfers);
+        while (g_hash_table_iter_next(&iter, &key, &value)) {
+            SpiceFileTransferTask *task = key;
+            spice_file_transfer_task_cancel(task);
+        }
+    }
+}
+
+static void
+task_cancel_cb(GtkButton *button,
+               gpointer user_data)
+{
+    SpiceFileTransferTask *task = SPICE_FILE_TRANSFER_TASK(user_data);
+    spice_file_transfer_task_cancel(task);
+}
+
+static TransferTaskWidgets *
+transfer_task_widgets_new(SpiceFileTransferTask *task)
+{
+    char *filename;
+    TransferTaskWidgets *widgets = g_new0(TransferTaskWidgets, 1);
+
+    widgets->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+    widgets->hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
+    widgets->cancel = gtk_button_new_with_label("Cancel");
+
+    widgets->progress = gtk_progress_bar_new();
+    filename = spice_file_transfer_task_get_filename(task);
+    widgets->label = gtk_label_new(filename);
+    g_free(filename);
+
+    gtk_widget_set_halign(widgets->label, GTK_ALIGN_START);
+    gtk_widget_set_valign(widgets->label, GTK_ALIGN_BASELINE);
+    gtk_widget_set_valign(widgets->progress, GTK_ALIGN_CENTER);
+    gtk_widget_set_hexpand(widgets->progress, TRUE);
+    gtk_widget_set_valign(widgets->cancel, GTK_ALIGN_CENTER);
+    gtk_widget_set_hexpand(widgets->progress, FALSE);
+
+    gtk_box_pack_start(GTK_BOX(widgets->hbox), widgets->progress,
+                       TRUE, TRUE, 0);
+    gtk_box_pack_start(GTK_BOX(widgets->hbox), widgets->cancel,
+                       FALSE, TRUE, 0);
+
+    gtk_box_pack_start(GTK_BOX(widgets->vbox), widgets->label,
+                       TRUE, TRUE, 0);
+    gtk_box_pack_start(GTK_BOX(widgets->vbox), widgets->hbox,
+                       TRUE, TRUE, 0);
+
+    g_signal_connect(widgets->cancel, "clicked",
+                     G_CALLBACK(task_cancel_cb), task);
+
+    gtk_widget_show_all(widgets->vbox);
+
+    return widgets;
+}
+
+static void
+transfer_task_widgets_free(TransferTaskWidgets *widgets)
+{
+    /* child widgets will be destroyed automatically */
+    gtk_widget_destroy(widgets->vbox);
+    g_free(widgets);
+}
+
+static void spice_connection_add_task(spice_connection *conn, SpiceFileTransferTask *task)
+{
+    TransferTaskWidgets *widgets;
+    GtkWidget *content = NULL;
+
+    g_signal_connect(task, "notify::progress",
+                     G_CALLBACK(transfer_update_progress), conn);
+    g_signal_connect(task, "finished",
+                     G_CALLBACK(transfer_task_finished), conn);
+    if (!conn->transfer_dialog) {
+        conn->transfer_dialog = gtk_dialog_new_with_buttons("File Transfers",
+                                                            GTK_WINDOW(conn->wins[0]->toplevel), 0,
+                                                            "Cancel", GTK_RESPONSE_CANCEL, NULL);
+        gtk_dialog_set_default_response(GTK_DIALOG(conn->transfer_dialog),
+                                        GTK_RESPONSE_CANCEL);
+        gtk_window_set_resizable(GTK_WINDOW(conn->transfer_dialog), FALSE);
+        g_signal_connect(conn->transfer_dialog, "response",
+                         G_CALLBACK(dialog_response_cb), conn);
+    }
+    gtk_widget_show(conn->transfer_dialog);
+    content = gtk_dialog_get_content_area(GTK_DIALOG(conn->transfer_dialog));
+    gtk_container_set_border_width(GTK_CONTAINER(content), 12);
+
+    widgets = transfer_task_widgets_new(task);
+    g_hash_table_insert(conn->transfers, g_object_ref(task), widgets);
+    gtk_box_pack_start(GTK_BOX(content),
+                       widgets->vbox, TRUE, TRUE, 6);
+}
+
+static void new_file_transfer(SpiceMainChannel *main, SpiceFileTransferTask *task, gpointer user_data)
+{
+    spice_connection *conn = user_data;
+    g_debug("new file transfer task");
+    spice_connection_add_task(conn, task);
+}
+
+static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data)
+{
+    spice_connection *conn = data;
+    int id;
+
+    g_object_get(channel, "channel-id", &id, NULL);
+    conn->channels++;
+    SPICE_DEBUG("new channel (#%d)", id);
+
+    if (SPICE_IS_MAIN_CHANNEL(channel)) {
+        SPICE_DEBUG("new main channel");
+        conn->main = SPICE_MAIN_CHANNEL(channel);
+        g_signal_connect(channel, "channel-event",
+                         G_CALLBACK(main_channel_event), conn);
+        g_signal_connect(channel, "main-mouse-update",
+                         G_CALLBACK(main_mouse_update), conn);
+        g_signal_connect(channel, "main-agent-update",
+                         G_CALLBACK(main_agent_update), conn);
+        g_signal_connect(channel, "new-file-transfer",
+                         G_CALLBACK(new_file_transfer), conn);
+        main_mouse_update(channel, conn);
+        main_agent_update(channel, conn);
+    }
+
+    if (SPICE_IS_DISPLAY_CHANNEL(channel)) {
+        if (id >= SPICE_N_ELEMENTS(conn->wins))
+            return;
+        if (conn->wins[id] != NULL)
+            return;
+        SPICE_DEBUG("new display channel (#%d)", id);
+        g_signal_connect(channel, "notify::monitors",
+                         G_CALLBACK(display_monitors), conn);
+        spice_channel_connect(channel);
+    }
+
+    if (SPICE_IS_INPUTS_CHANNEL(channel)) {
+        SPICE_DEBUG("new inputs channel");
+        g_signal_connect(channel, "inputs-modifiers",
+                         G_CALLBACK(inputs_modifiers), conn);
+    }
+
+    if (SPICE_IS_PLAYBACK_CHANNEL(channel)) {
+        SPICE_DEBUG("new audio channel");
+        conn->audio = spice_audio_get(s, NULL);
+    }
+
+    if (SPICE_IS_USBREDIR_CHANNEL(channel)) {
+        update_auto_usbredir_sensitive(conn);
+    }
+
+    if (SPICE_IS_PORT_CHANNEL(channel)) {
+        g_signal_connect(channel, "notify::port-opened",
+                         G_CALLBACK(port_opened), conn);
+        g_signal_connect(channel, "port-data",
+                         G_CALLBACK(port_data), conn);
+        spice_channel_connect(channel);
+    }
+}
+
+static void channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer data)
+{
+    spice_connection *conn = data;
+    int id;
+
+    g_object_get(channel, "channel-id", &id, NULL);
+    if (SPICE_IS_MAIN_CHANNEL(channel)) {
+        SPICE_DEBUG("zap main channel");
+        conn->main = NULL;
+    }
+
+    if (SPICE_IS_DISPLAY_CHANNEL(channel)) {
+        if (id >= SPICE_N_ELEMENTS(conn->wins))
+            return;
+        SPICE_DEBUG("zap display channel (#%d)", id);
+        /* FIXME destroy widget only */
+    }
+
+    if (SPICE_IS_PLAYBACK_CHANNEL(channel)) {
+        SPICE_DEBUG("zap audio channel");
+    }
+
+    if (SPICE_IS_USBREDIR_CHANNEL(channel)) {
+        update_auto_usbredir_sensitive(conn);
+    }
+
+    if (SPICE_IS_PORT_CHANNEL(channel)) {
+        if (SPICE_PORT_CHANNEL(channel) == stdin_port)
+            stdin_port = NULL;
+    }
+
+    conn->channels--;
+    if (conn->channels > 0) {
+        return;
+    }
+
+    connection_destroy(conn);
+}
+
+static void migration_state(GObject *session,
+                            GParamSpec *pspec, gpointer data)
+{
+    SpiceSessionMigration mig;
+
+    g_object_get(session, "migration-state", &mig, NULL);
+    if (mig == SPICE_SESSION_MIGRATION_SWITCHING)
+        g_message("migrating session");
+}
+
+static spice_connection *connection_new(void)
+{
+    spice_connection *conn;
+    SpiceUsbDeviceManager *manager;
+
+    conn = g_new0(spice_connection, 1);
+    conn->session = spice_session_new();
+    conn->gtk_session = spice_gtk_session_get(conn->session);
+    g_signal_connect(conn->session, "channel-new",
+                     G_CALLBACK(channel_new), conn);
+    g_signal_connect(conn->session, "channel-destroy",
+                     G_CALLBACK(channel_destroy), conn);
+    g_signal_connect(conn->session, "notify::migration-state",
+                     G_CALLBACK(migration_state), conn);
+
+    manager = spice_usb_device_manager_get(conn->session, NULL);
+    if (manager) {
+        g_signal_connect(manager, "auto-connect-failed",
+                         G_CALLBACK(usb_connect_failed), NULL);
+        g_signal_connect(manager, "device-error",
+                         G_CALLBACK(usb_connect_failed), NULL);
+    }
+
+    conn->transfers = g_hash_table_new_full(g_direct_hash, g_direct_equal,
+                                            g_object_unref,
+                                            (GDestroyNotify)transfer_task_widgets_free);
+    connections++;
+    SPICE_DEBUG("%s (%d)", __FUNCTION__, connections);
+    return conn;
+}
+
+static void connection_connect(spice_connection *conn)
+{
+    conn->disconnecting = false;
+    spice_session_connect(conn->session);
+}
+
+static void connection_disconnect(spice_connection *conn)
+{
+    if (conn->disconnecting)
+        return;
+    conn->disconnecting = true;
+    spice_session_disconnect(conn->session);
+}
+
+static void connection_destroy(spice_connection *conn)
+{
+    g_object_unref(conn->session);
+    g_hash_table_unref(conn->transfers);
+    free(conn);
+
+    connections--;
+    SPICE_DEBUG("%s (%d)", __FUNCTION__, connections);
+    if (connections > 0) {
+        return;
+    }
+
+    g_main_loop_quit(mainloop);
+}
+
+/* ------------------------------------------------------------------ */
+
+static GOptionEntry cmd_entries[] = {
+    {
+        .long_name        = "full-screen",
+        .short_name       = 'f',
+        .arg              = G_OPTION_ARG_NONE,
+        .arg_data         = &fullscreen,
+        .description      = "Open in full screen mode",
+    },{
+        .long_name        = "version",
+        .arg              = G_OPTION_ARG_NONE,
+        .arg_data         = &version,
+        .description      = "Display version and quit",
+    },{
+        .long_name        = "title",
+        .arg              = G_OPTION_ARG_STRING,
+        .arg_data         = &spicy_title,
+        .description      = "Set the window title",
+        .arg_description  = "<title>",
+    },{
+        /* end of list */
+    }
+};
+
+static void usb_connect_failed(GObject               *object,
+                               SpiceUsbDevice        *device,
+                               GError                *error,
+                               gpointer               data)
+{
+    GtkWidget *dialog;
+
+    if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED)
+        return;
+
+    dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR,
+                                    GTK_BUTTONS_CLOSE,
+                                    "USB redirection error");
+    gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
+                                             "%s", error->message);
+    gtk_dialog_run(GTK_DIALOG(dialog));
+    gtk_widget_destroy(dialog);
+}
+
+static void setup_terminal(gboolean reset)
+{
+    int stdinfd = fileno(stdin);
+
+    if (!isatty(stdinfd))
+        return;
+
+#ifdef HAVE_TERMIOS_H
+    struct termios tios;
+    static struct termios saved_tios;
+    static bool saved = false;
+
+    if (reset) {
+        if (!saved)
+            return;
+        tios = saved_tios;
+    } else {
+        tcgetattr(stdinfd, &tios);
+        saved_tios = tios;
+        saved = true;
+        tios.c_lflag &= ~(ICANON | ECHO);
+    }
+
+    tcsetattr(stdinfd, TCSANOW, &tios);
+#endif
+}
+
+static void watch_stdin(void)
+{
+    int stdinfd = fileno(stdin);
+    GIOChannel *gin;
+
+    setup_terminal(false);
+    gin = g_io_channel_unix_new(stdinfd);
+    g_io_channel_set_flags(gin, G_IO_FLAG_NONBLOCK, NULL);
+    g_io_add_watch(gin, G_IO_IN|G_IO_ERR|G_IO_HUP|G_IO_NVAL, input_cb, NULL);
+}
+
+int main(int argc, char *argv[])
+{
+    GError *error = NULL;
+    GOptionContext *context;
+    spice_connection *conn;
+    gchar *conf_file, *conf;
+    char *host = NULL, *port = NULL, *tls_port = NULL, *unix_path = NULL;
+
+    keyfile = g_key_file_new();
+
+    int mode = S_IRWXU;
+    conf_file = g_build_filename(g_get_user_config_dir(), "spicy", NULL);
+    if (g_mkdir_with_parents(conf_file, mode) == -1)
+        SPICE_DEBUG("failed to create config directory");
+    g_free(conf_file);
+
+    conf_file = g_build_filename(g_get_user_config_dir(), "spicy", "settings", NULL);
+    if (!g_key_file_load_from_file(keyfile, conf_file,
+                                   G_KEY_FILE_KEEP_COMMENTS|G_KEY_FILE_KEEP_TRANSLATIONS, &error)) {
+        SPICE_DEBUG("Couldn't load configuration: %s", error->message);
+        g_clear_error(&error);
+    }
+
+    /* parse opts */
+    gtk_init(&argc, &argv);
+    context = g_option_context_new("- spice client test application");
+    g_option_context_set_summary(context, "Gtk+ test client to connect to Spice servers.");
+    g_option_context_set_description(context, "Report bugs to " PACKAGE_BUGREPORT ".");
+    g_option_context_add_group(context, spice_get_option_group());
+    g_option_context_set_main_group(context, spice_cmdline_get_option_group());
+    g_option_context_add_main_entries(context, cmd_entries, NULL);
+    g_option_context_add_group(context, gtk_get_option_group(TRUE));
+    if (!g_option_context_parse (context, &argc, &argv, &error)) {
+        g_print("option parsing failed: %s\n", error->message);
+        exit(1);
+    }
+    g_option_context_free(context);
+
+    if (version) {
+        g_print("spicy " PACKAGE_VERSION "\n");
+        exit(0);
+    }
+
+    mainloop = g_main_loop_new(NULL, false);
+
+    conn = connection_new();
+    spice_set_session_option(conn->session);
+    spice_cmdline_session_setup(conn->session);
+
+    g_object_get(conn->session,
+                 "unix-path", &unix_path,
+                 "host", &host,
+                 "port", &port,
+                 "tls-port", &tls_port,
+                 NULL);
+    /* If user doesn't provide hostname and port, show the dialog window
+       instead of connecting to server automatically */
+    if ((host == NULL || (port == NULL && tls_port == NULL)) && unix_path == NULL) {
+        if (!spicy_connect_dialog(conn->session)) {
+            exit(0);
+        }
+    }
+    g_free(host);
+    g_free(port);
+    g_free(tls_port);
+    g_free(unix_path);
+
+    connection_connect(conn);
+    if (connections > 0)
+        g_main_loop_run(mainloop);
+    g_main_loop_unref(mainloop);
+
+    if ((conf = g_key_file_to_data(keyfile, NULL, &error)) == NULL ||
+        !g_file_set_contents(conf_file, conf, -1, &error)) {
+        SPICE_DEBUG("Couldn't save configuration: %s", error->message);
+        g_error_free(error);
+        error = NULL;
+    }
+
+    g_free(conf_file);
+    g_free(conf);
+    g_key_file_free(keyfile);
+
+    g_free(spicy_title);
+
+    setup_terminal(true);
+    return 0;
+}


More information about the Spice-commits mailing list